From 78a5f312400d7d5c8523ffca6e6b5b60cacd42c5 Mon Sep 17 00:00:00 2001 From: Tony Oliveira Date: Sun, 22 Nov 2020 12:31:26 +0000 Subject: [PATCH 01/11] Added UpdateServicesComputerTargetGroup (tests passed) --- CHANGELOG.md | 9 + ReadMe.md | 6 + .../Helpers/ImitateUpdateServicesModule.psm1 | 125 ++++++- ...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 +++ 9 files changed, 821 insertions(+), 1 deletion(-) 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 40e327b..9a077c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ 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. @@ -14,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Changed default timeout in Wait-Win32ProcessStart function for cab installation. - Updated pester test to support pester v5 - Updated ReadMe.md to removed `RunRuleNow` parameter. +- Updated ImitateUpdateServicesModule Module to meet style guidelines ### Added diff --git a/ReadMe.md b/ReadMe.md index c2f365a..003dea4 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/Tests/Helpers/ImitateUpdateServicesModule.psm1 b/Tests/Helpers/ImitateUpdateServicesModule.psm1 index 74dd5e2..cf9654c 100644 --- a/Tests/Helpers/ImitateUpdateServicesModule.psm1 +++ b/Tests/Helpers/ImitateUpdateServicesModule.psm1 @@ -2,7 +2,7 @@ function Get-WsusServerTemplate { $WsusServer = [pscustomobject] @{ Name = 'ServerName' - } + } $ApprovalRule = [scriptblock]{ $ApprovalRule = [pscustomobject]@{ @@ -52,6 +52,127 @@ function Get-WsusServerTemplate 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 @@ -60,6 +181,8 @@ function Get-WsusServerTemplate $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 { 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 c0df0712443f01f8426aa9aa0a1d993ceff0de39 Mon Sep 17 00:00:00 2001 From: Tony Oliveira Date: Mon, 23 Nov 2020 12:04:15 +0000 Subject: [PATCH 02/11] 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 6f43af296393df1e7946d1ab5c04d46d427cd064 Mon Sep 17 00:00:00 2001 From: Tony Oliveira Date: Mon, 23 Nov 2020 12:05:40 +0000 Subject: [PATCH 03/11] 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 a94de4e83d2abc0b2ccdbd62d6f3a740c5fdfa80 Mon Sep 17 00:00:00 2001 From: Tony Oliveira Date: Tue, 24 Nov 2020 12:27:00 +0000 Subject: [PATCH 04/11] 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. From 6ee12a24c1dd4a6c9bd37324d9d76b39598c6d5b Mon Sep 17 00:00:00 2001 From: Chris Hill Date: Thu, 30 May 2024 16:05:23 +0100 Subject: [PATCH 05/11] Update CHANGELOG; spelling/case fixes --- CHANGELOG.md | 6 +----- Tests/Helpers/ImitateUpdateServicesModule.psm1 | 2 +- Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 | 6 +++--- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a077c9..edaf79a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Added UpdateServicesComputerTargetGroup Resource including: - - Resource - - Unit Tests - - Example - - Update to ReadMe File +- Added UpdateServicesComputerTargetGroup Resource to manage computer target groups ### Changed diff --git a/Tests/Helpers/ImitateUpdateServicesModule.psm1 b/Tests/Helpers/ImitateUpdateServicesModule.psm1 index cf9654c..c11059b 100644 --- a/Tests/Helpers/ImitateUpdateServicesModule.psm1 +++ b/Tests/Helpers/ImitateUpdateServicesModule.psm1 @@ -149,7 +149,7 @@ function Get-WsusServerTemplate return $this.ChildTargetGroup } - Add-Member -InputOBject $ComputerTargetGroup.ChildTargetGroup -MemberType ScriptMethod -Name Delete -Value {} + Add-Member -InputObject $ComputerTargetGroup.ChildTargetGroup -MemberType ScriptMethod -Name Delete -Value {} } } diff --git a/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 b/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 index 5098ad4..788f5c4 100644 --- a/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 +++ b/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 @@ -76,9 +76,9 @@ try $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' + $script:resource.Id | Should -Be $null + $script:resource.Name | Should -Be 'Servers' + $script:resource.Path | Should -Be 'All Computers' } } From d7bdaecfd17fa0e0e31ffe5ca8e1154fbd52a019 Mon Sep 17 00:00:00 2001 From: Chris Hill Date: Mon, 10 Jun 2024 15:57:37 +0100 Subject: [PATCH 06/11] Update localization strings --- .../MSFT_UpdateServicesComputerTargetGroup.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/MSFT_UpdateServicesComputerTargetGroup.psm1 b/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/MSFT_UpdateServicesComputerTargetGroup.psm1 index 2df8ffb..b59468c 100644 --- a/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/MSFT_UpdateServicesComputerTargetGroup.psm1 +++ b/source/DSCResources/MSFT_UpdateServicesComputerTargetGroup/MSFT_UpdateServicesComputerTargetGroup.psm1 @@ -3,7 +3,7 @@ # 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' +$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' <# From b6f9e327b698ff178ed6767e5310ef763d234775 Mon Sep 17 00:00:00 2001 From: Chris Hill Date: Fri, 14 Jun 2024 16:26:05 +0100 Subject: [PATCH 07/11] Fix tests --- .../Helpers/ImitateUpdateServicesModule.psm1 | 2 -- ...pdateServicesComputerTargetGroup.tests.ps1 | 20 +++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Tests/Helpers/ImitateUpdateServicesModule.psm1 b/Tests/Helpers/ImitateUpdateServicesModule.psm1 index c11059b..7309c53 100644 --- a/Tests/Helpers/ImitateUpdateServicesModule.psm1 +++ b/Tests/Helpers/ImitateUpdateServicesModule.psm1 @@ -179,8 +179,6 @@ function Get-WsusServerTemplate $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 {} diff --git a/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 b/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 index 788f5c4..22e2f79 100644 --- a/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 +++ b/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 @@ -12,6 +12,9 @@ $TestEnvironment = Initialize-TestEnvironment ` -ResourceType 'Mof' ` -TestType Unit +#region Pester Test Initialization +Import-Module $PSScriptRoot\..\Helpers\ImitateUpdateServicesModule.psm1 -force -ErrorAction Stop + #endregion HEADER @@ -20,11 +23,6 @@ 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 @@ -55,7 +53,9 @@ try if (Test-Path -Path variable:script:resource) { Remove-Variable -Scope 'script' -Name 'resource' } } - Mock -CommandName Write-Verbose -MockWith {} + BeforeAll { + Mock -CommandName Write-Verbose -MockWith {} + } Context 'An error occurs retrieving WSUS Server configuration information.' { Mock -CommandName Get-WsusServer -MockWith { throw 'An error occurred.' } @@ -121,7 +121,9 @@ try #region Function Test-TargetResource Describe "MSFT_UpdateServicesComputerTargetGroup\Test-TargetResource." { - Mock -CommandName Write-Verbose -MockWith {} + BeforeAll { + 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 { @@ -211,7 +213,9 @@ try if (Test-Path -Path variable:script:resource) { Remove-Variable -Scope 'script' -Name 'resource' } } - Mock -CommandName Write-Verbose -MockWith {} + BeforeAll { + Mock -CommandName Write-Verbose -MockWith {} + } Context 'An error occurs retrieving WSUS Server configuration information.' { Mock -CommandName Get-WsusServer -MockWith { throw 'An error occurred.' } From 30227790999db81ee2c6066eff14edfc642672a6 Mon Sep 17 00:00:00 2001 From: Chris Hill Date: Fri, 14 Jun 2024 16:26:35 +0100 Subject: [PATCH 08/11] Update localization strings --- source/Modules/PDT/PDT.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Modules/PDT/PDT.psm1 b/source/Modules/PDT/PDT.psm1 index ec00fe6..450dd40 100644 --- a/source/Modules/PDT/PDT.psm1 +++ b/source/Modules/PDT/PDT.psm1 @@ -1,6 +1,6 @@ $script:resourceHelperModulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\DscResource.Common' Import-Module -Name $script:resourceHelperModulePath -ErrorAction Stop -$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' -FileName 'PDT.strings.psd1' +$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' # New-InvalidArgumentError # New-InvalidArgumentException -ArgumentName 'Action' -Message $errorMessage From 2011471079aafc5e0b4da10101383bd0eb358f55 Mon Sep 17 00:00:00 2001 From: Chris Hill Date: Fri, 14 Jun 2024 17:43:27 +0100 Subject: [PATCH 09/11] Fix spellings --- Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 b/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 index 22e2f79..0f762c7 100644 --- a/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 +++ b/Tests/Unit/MSFT_UpdateServicesComputerTargetGroup.tests.ps1 @@ -60,7 +60,7 @@ try 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.' { + It 'Calling Get should throw when an error occurs 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 @@ -70,7 +70,7 @@ try 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.' { + It 'Calling Get should not throw when the WSUS Server is not yet configured / 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 @@ -220,7 +220,7 @@ try 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.' { + It 'Calling Set should throw when an error occurs 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 From 187510ace3356f8790127096a79beafee1cd13c1 Mon Sep 17 00:00:00 2001 From: Chris Hill Date: Thu, 20 Jun 2024 14:29:55 +0100 Subject: [PATCH 10/11] Update DscResourcesToExport --- source/UpdateServicesDsc.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/UpdateServicesDsc.psd1 b/source/UpdateServicesDsc.psd1 index 67e186d..3e71343 100644 --- a/source/UpdateServicesDsc.psd1 +++ b/source/UpdateServicesDsc.psd1 @@ -73,7 +73,7 @@ AliasesToExport = @() # DSC resources to export from this module - DscResourcesToExport = @('UpdateServicesApprovalRule', 'UpdateServicesCleanup','UpdateServicesServer') + DscResourcesToExport = @('UpdateServicesApprovalRule', 'UpdateServicesCleanup','UpdateServicesComputerTargetGroup','UpdateServicesServer') # List of all modules packaged with this module # ModuleList = @() From dab62a71b08ca5ad80a192437fe43d2a6d0587b6 Mon Sep 17 00:00:00 2001 From: Chris Hill Date: Wed, 28 Aug 2024 16:54:38 +0100 Subject: [PATCH 11/11] Pin GitVersion to 5.x to resolve build errors --- CHANGELOG.md | 2 ++ azure-pipelines.yml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0aad6ea..b979a57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,8 @@ multiple products for the same `Title`. - Fix issue [#63](https://github.com/dsccommunity/UpdateServicesDsc/issues/63), Fixed verbose output of WSUS server in UpdateServicesApprovalRule - Fixed the `azure-pipelines.yml` to trigger on main not master. +- Update build process to pin GitVersion to 5.* to resolve errors + (https://github.com/gaelcolas/Sampler/issues/477). ## [1.2.0] - 2020-05-18 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 9160e45..7a18eda 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -26,7 +26,7 @@ stages: vmImage: 'ubuntu-latest' steps: - pwsh: | - dotnet tool install --global GitVersion.Tool + dotnet tool install --global GitVersion.Tool --version 5.* $gitVersionObject = dotnet-gitversion | ConvertFrom-Json $gitVersionObject.PSObject.Properties.ForEach{ Write-Host -Object "Setting Task Variable '$($_.Name)' with value '$($_.Value)'."