diff --git a/DSCResource.Tests b/DSCResource.Tests new file mode 160000 index 0000000..e7b3589 --- /dev/null +++ b/DSCResource.Tests @@ -0,0 +1 @@ +Subproject commit e7b3589bfba4c80e131ea6baf4f9f712336b6390 diff --git a/DSCResources/MSFT_UpdateServicesServer/MSFT_UpdateServicesServer.psm1 b/DSCResources/MSFT_UpdateServicesServer/MSFT_UpdateServicesServer.psm1 index 9024213..7a77503 100644 --- a/DSCResources/MSFT_UpdateServicesServer/MSFT_UpdateServicesServer.psm1 +++ b/DSCResources/MSFT_UpdateServicesServer/MSFT_UpdateServicesServer.psm1 @@ -1,7 +1,7 @@ # DSC resource to initialize and configure WSUS Server. # Classifications ID reference... -# +# # Applications = 5C9376AB-8CE6-464A-B136-22113DD69801 # Connectors = 434DE588-ED14-48F5-8EED-A15E09A991F6 # Critical Updates = E6CF1350-C01B-414D-A61F-263D14D133B4 @@ -63,16 +63,16 @@ function Get-TargetResource $WsusConfiguration = $WsusServer.GetConfiguration() Write-Verbose -Message 'Getting WSUSServer subscription' $WsusSubscription = $WsusServer.GetSubscription() - + Write-Verbose -Message 'Getting WSUSServer SQL Server' $SQLServer = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Update Services\Server\Setup' ` - -Name 'SQLServerName').SQLServerName + -Name 'SQLServerName').SQLServerName Write-Verbose -Message "WSUSServer SQL Server is $SQLServer" Write-Verbose -Message 'Getting WSUSServer content directory' $ContentDir = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Update Services\Server\Setup' ` - -Name 'ContentDir').ContentDir + -Name 'ContentDir').ContentDir Write-Verbose -Message "WSUSServer content directory is $ContentDir" - + Write-Verbose -Message 'Getting WSUSServer update improvement program' $UpdateImprovementProgram = $WsusConfiguration.MURollupOptin Write-Verbose -Message "WSUSServer content update improvement program is $UpdateImprovementProgram" @@ -94,7 +94,7 @@ function Get-TargetResource $UpstreamServerSSL = $null $UpstreamServerReplica = $null } - + if($WsusConfiguration.UseProxy) { Write-Verbose -Message 'Getting WSUSServer proxy server' @@ -128,9 +128,9 @@ function Get-TargetResource Write-Verbose -Message "WSUSServer languages are $Languages" Write-Verbose -Message 'Getting WSUSServer classifications' - if ($Classifications = @($WsusSubscription.GetUpdateClassifications().ID.Guid)) - { - if($null -eq (Compare-Object -ReferenceObject ($Classifications | Sort-Object -Unique) -DifferenceObject (($WsusServer.GetUpdateClassifications().ID.Guid) | Sort-Object -Unique) -SyncWindow 0)) + if ($Classifications = @($WsusSubscription.GetUpdateClassifications().ID.Guid)) { + if($null -eq (Compare-Object -ReferenceObject ($Classifications | Sort-Object -Unique) -DifferenceObject ` + (($WsusServer.GetUpdateClassifications().ID.Guid) | Sort-Object -Unique) -SyncWindow 0)) { $Classifications = @("*") } @@ -140,9 +140,9 @@ function Get-TargetResource } Write-Verbose -Message "WSUSServer classifications are $Classifications" Write-Verbose -Message 'Getting WSUSServer products' - if ($Products = @($WsusSubscription.GetUpdateCategories().Title)) - { - if($null -eq (Compare-Object -ReferenceObject ($Products | Sort-Object -Unique) -DifferenceObject (($WsusServer.GetUpdateCategories().Title) | Sort-Object -Unique) -SyncWindow 0)) + if ($Products = @($WsusSubscription.GetUpdateCategories().Title)) { + if($null -eq (Compare-Object -ReferenceObject ($Products | Sort-Object -Unique) -DifferenceObject ` + (($WsusServer.GetUpdateCategories().Title) | Sort-Object -Unique) -SyncWindow 0)) { $Products = @("*") } @@ -158,6 +158,8 @@ function Get-TargetResource Write-Verbose -Message "WSUSServer synchronize automatically time of day is $SynchronizeAutomaticallyTimeOfDay" $SynchronizationsPerDay = $WsusSubscription.NumberOfSynchronizationsPerDay Write-Verbose -Message "WSUSServer number of synchronizations per day is $SynchronizationsPerDay" + $ClientTargetingMode = $WsusConfiguration.TargetingMode + Write-Verbose -Message "WSUSServer client targeting mode is $ClientTargetingMode" } $returnValue = @{ @@ -179,6 +181,7 @@ function Get-TargetResource SynchronizeAutomatically = $SynchronizeAutomatically SynchronizeAutomaticallyTimeOfDay = $SynchronizeAutomaticallyTimeOfDay SynchronizationsPerDay = $SynchronizationsPerDay + ClientTargetingMode = $ClientTargetingMode } $returnValue @@ -229,6 +232,9 @@ function Get-TargetResource Number of automatic synchronizations per day .PARAMETER Synchronize Run a synchronization immediately when running Set + .PARAMETER ClientTargetingMode + An enumerated value that describes if how the Target Groups are populated. + Accepts 'Client'(default) or 'Server'. #> function Set-TargetResource { @@ -314,7 +320,11 @@ function Set-TargetResource [Parameter()] [System.Boolean] - $Synchronize + $Synchronize, + + [ValidateSet("Client", "Server")] + [System.String] + $ClientTargetingMode ) # Is WSUS configured? @@ -356,11 +366,11 @@ function Set-TargetResource Write-Verbose -Message [string]$Process Wait-Win32ProcessEnd -Path $Path -Arguments $Arguments } - else + else { $Process = Start-Win32Process -Path $Path -Arguments $Arguments Write-Verbose -Message [string]$Process - Wait-Win32ProcessEnd -Path $Path -Arguments $Arguments + Wait-Win32ProcessEnd -Path $Path -Arguments $Arguments } } @@ -450,6 +460,13 @@ function Set-TargetResource $WsusConfiguration.SetEnabledUpdateLanguages($Languages) } + #ClientTargetingMode + if($PSBoundParameters.ContainsKey("Client Targeting Mode")) + { + Write-Verbose -Message "Setting WSUS client targeting mode" + $WsusConfiguration.TargetingMode = $ClientTargetingMode + } + # Save configuration before initial sync SaveWsusConfiguration @@ -508,11 +525,11 @@ function Set-TargetResource Write-Verbose -Message [string]$Process Wait-Win32ProcessEnd -Path $Path -Arguments $Arguments } - else + else { $Process = Start-Win32Process -Path $Path -Arguments $Arguments Write-Verbose -Message [string]$Process - Wait-Win32ProcessEnd -Path $Path -Arguments $Arguments + Wait-Win32ProcessEnd -Path $Path -Arguments $Arguments } $WsusConfiguration.OobeInitialized = $true @@ -563,10 +580,11 @@ function Set-TargetResource { foreach($Classification in $Classifications) { - if($WsusClassification = $AllWsusClassifications | Where-Object {$_.ID.Guid -eq $Classification}) + if($WsusClassification = $AllWsusClassifications | ` + Where-Object {$_.ID.Guid -eq $Classification}) { $null = $ClassificationCollection.Add($WsusServer.GetUpdateClassification(` - $WsusClassification.Id)) + $WsusClassification.Id)) } else { @@ -590,7 +608,7 @@ function Set-TargetResource if($Synchronize) { Write-Verbose -Message "Synchronizing WSUS" - + $WsusServer.GetSubscription().StartSynchronization() while($WsusServer.GetSubscription().GetSynchronizationStatus() -eq 'Running') { @@ -659,6 +677,9 @@ function Set-TargetResource Number of automatic synchronizations per day .PARAMETER Synchronize Run a synchronization immediately when running Set + .PARAMETER ClientTargetingMode + An enumerated value that describes if how the Target Groups are populated. + Accepts 'Client'(default) or 'Server'. #> function Test-TargetResource { @@ -745,7 +766,11 @@ function Test-TargetResource [Parameter()] [System.Boolean] - $Synchronize + $Synchronize, + + [ValidateSet("Client", "Server")] + [System.String] + $ClientTargetingMode ) $result = $true @@ -838,20 +863,23 @@ function Test-TargetResource } } else { - if($null -ne (Compare-Object -ReferenceObject ($Wsus.Languages | Sort-Object -Unique) -DifferenceObject ($Languages | Sort-Object -Unique) -SyncWindow 0)) + if((Compare-Object -ReferenceObject ($Wsus.Languages | Sort-Object -Unique) ` + -DifferenceObject ($Languages | Sort-Object -Unique) -SyncWindow 0) -ne $null) { Write-Verbose -Message "Languages test failed" $result = $false - } + } } # Test Products - if($null -ne (Compare-Object -ReferenceObject ($Wsus.Products | Sort-Object -Unique) -DifferenceObject ($Products | Sort-Object -Unique) -SyncWindow 0)) + if((Compare-Object -ReferenceObject ($Wsus.Products | Sort-Object -Unique) ` + -DifferenceObject ($Products | Sort-Object -Unique) -SyncWindow 0) -ne $null) { Write-Verbose -Message "Products test failed" $result = $false } # Test Classifications - if($null -ne (Compare-Object -ReferenceObject ($Wsus.Classifications | Sort-Object -Unique) -DifferenceObject ($Classifications | Sort-Object -Unique) -SyncWindow 0)) + if((Compare-Object -ReferenceObject ($Wsus.Classifications | Sort-Object -Unique) ` + -DifferenceObject ($Classifications | Sort-Object -Unique) -SyncWindow 0) -ne $null) { Write-Verbose -Message "Classifications test failed" $result = $false @@ -873,6 +901,19 @@ function Test-TargetResource $result = $false } } + + # Test Client Targeting Mode + if($ClientTargetingMode) + { + if($PSBoundParameters.ContainsKey('ClientTargetingMode')) + { + if($Wsus.ClientTargetingMode -ne $ClientTargetingMode) + { + Write-Verbose -Message "ClientTargetingMode test failed" + $result = $false + } + } + } } $result @@ -897,7 +938,7 @@ function SaveWsusConfiguration Start-Sleep -Seconds 1 } } - until($WsusConfigurationReady) + until($WsusConfigurationReady) } diff --git a/DSCResources/MSFT_UpdateServicesServer/MSFT_UpdateServicesServer.schema.mof b/DSCResources/MSFT_UpdateServicesServer/MSFT_UpdateServicesServer.schema.mof index 455857d..d36cdc9 100644 --- a/DSCResources/MSFT_UpdateServicesServer/MSFT_UpdateServicesServer.schema.mof +++ b/DSCResources/MSFT_UpdateServicesServer/MSFT_UpdateServicesServer.schema.mof @@ -22,4 +22,5 @@ class MSFT_UpdateServicesServer : OMI_BaseResource [Write, Description("First synchronization.")] String SynchronizeAutomaticallyTimeOfDay; [Write, Description("Synchronizations per day.")] UInt16 SynchronizationsPerDay; [Write, Description("Begin initial synchronization.")] Boolean Synchronize; + [write, Description("An enumerated value that describes if how the Target Groups are populated.\nClient {default} \nServer \n"), ValueMap{"Client","Server"}, Values{"Client","Server"}] String ClientTargetingMode; }; diff --git a/ReadMe.md b/ReadMe.md index 6022cb3..f1c8c19 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -21,6 +21,7 @@ This is the branch containing the latest release - no contributions should be made directly to this branch. ### dev + [![Build status](https://ci.appveyor.com/api/projects/status/wncsr23e1fqbv4nt/branch/dev??svg=true)](https://ci.appveyor.com/project/mgreenegit/UpdateServicesDsc/branch/dev) [![codecov](https://codecov.io/gh/mgreenegit/UpdateServicesDsc/branch/dev/graph/badge.svg)](https://codecov.io/gh/mgreenegit/UpdateServicesDsc/branch/dev) @@ -144,9 +145,7 @@ Windows Server 2008 R2 SP1, Windows Server 2012 and Windows Server 2012 R2. * **SynchronizationsPerDay**: Synchronizations per day. * **Synchronize**: Begin initial synchronization. * **RunRuleNow**: Run Approval Rule on existing content. - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). -For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. +* **ClientTargetingMode**: An enumerated value that describes if how the Target Groups are populated. ## Versions diff --git a/Tests/.Unit/MSFT_UpdateServicesServer.tests.ps1 b/Tests/.Unit/MSFT_UpdateServicesServer.tests.ps1 index b98e035..f62380b 100644 --- a/Tests/.Unit/MSFT_UpdateServicesServer.tests.ps1 +++ b/Tests/.Unit/MSFT_UpdateServicesServer.tests.ps1 @@ -29,7 +29,7 @@ Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHel $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $Global:DSCModuleName ` -DSCResourceName $Global:DSCResourceName ` - -TestType Unit + -TestType Unit #endregion # Begin Testing @@ -63,6 +63,7 @@ try SynchronizeAutomatically = $true SynchronizeAutomaticallyTimeOfDay = '04:00:00' SynchronizationsPerDay = 24 + ClientTargetingMode = "Client" } $DSCTestValues = @{ @@ -82,9 +83,10 @@ try SynchronizeAutomatically = $true SynchronizeAutomaticallyTimeOfDay = '04:00:00' SynchronizationsPerDay = 24 + ClientTargetingMode = "Client" } #endregion - + #region Function Get-TargetResource expecting Ensure Present Describe "$($Global:DSCResourceName)\Get-TargetResource" { @@ -96,11 +98,11 @@ try it 'calling Get should not throw' { {$Script:resource = Get-TargetResource -Ensure 'Present' -verbose} | should not throw } - + it 'sets the value for Ensure' { $Script:resource.Ensure | should be 'Present' } - + foreach ($setting in $DSCSetValues.Keys) { it "returns $setting in Get results" { $Script:resource.$setting | should be $DSCGetReturnValues.$setting @@ -118,7 +120,7 @@ try Mock -CommandName Get-WSUSServer -MockWith {} {$Script:resource = Get-TargetResource -Ensure 'Absent' -verbose} | should not throw } - + it 'sets the value for Ensure' { $Script:resource.Ensure | should be 'Absent' } @@ -141,7 +143,7 @@ try Mock -CommandName Get-TargetResource -MockWith {$DSCTestValues} -Verifiable $script:result = $null - + it 'calling test should not throw' { {$script:result = Test-TargetResource @DSCTestValues -verbose} | should not throw } @@ -149,21 +151,21 @@ try it "result should be true" { $script:result | should be $true } - + it 'mocks were called' { Assert-VerifiableMock } } - + Context 'server should not be configured (Ensure=Absent)' { - + $DSCTestValues.Remove('Ensure') $DSCTestValues.Add('Ensure','Absent') Mock -CommandName Get-TargetResource -MockWith {$DSCTestValues} -Verifiable - + $script:result = $null - + it 'calling test should not throw' { {$script:result = Test-TargetResource @DSCTestValues -verbose} | should not throw } @@ -178,13 +180,13 @@ try } Context 'server should be configured correctly but is not' { - + $DSCTestValues.Remove('Ensure') Mock -CommandName Get-TargetResource -MockWith {$DSCTestValues} -Verifiable - + $script:result = $null - + it 'calling test should not throw' { {$script:result = Test-TargetResource @DSCTestValues -Ensure 'Present' -verbose} | should not throw } @@ -199,21 +201,21 @@ try } Context "setting has drifted" { - + $DSCTestValues.Remove('Ensure') $DSCTestValues.Add('Ensure','Present') # Settings not currently tested: ProxyServerUserName, ProxyServerCredential, ProxyServerBasicAuthentication, 'Languages', 'Products', 'Classifications', 'SynchronizeAutomatically' $settingsList = 'UpdateImprovementProgram', 'UpstreamServerName', 'UpstreamServerPort', 'UpstreamServerSSL', 'UpstreamServerReplica', 'ProxyServerName', 'ProxyServerPort', 'SynchronizeAutomaticallyTimeOfDay', 'SynchronizationsPerDay' foreach ($setting in $settingsList) { - + Mock -CommandName Get-TargetResource -MockWith { $DSCTestValues.Remove("$setting") $DSCTestValues } -Verifiable - + $script:result = $null - + it "calling test with change to $setting should not throw" { {$script:result = Test-TargetResource @DSCTestValues -verbose} | should not throw } @@ -235,9 +237,9 @@ try #region Function Set-TargetResource Describe "$($Global:DSCResourceName)\Set-TargetResource" { - + $DSCTestValues.Remove('Ensure') - + Mock -CommandName Test-TargetResource -MockWith {$true} Mock -CommandName New-TerminatingError -MockWith {} Mock SaveWsusConfiguration -MockWith {} @@ -257,7 +259,7 @@ try Assert-MockCalled -CommandName New-TerminatingError -Times 0 } } - + Context 'resource supports Ensure=Absent' { it 'should not throw when running on a properly configured server' { @@ -274,7 +276,7 @@ try } } } - #endregion + #endregion } } diff --git a/Tests/Integration/UpdateServicesConfig.ps1 b/Tests/Integration/UpdateServicesConfig.ps1 index d98d626..9bdd35c 100644 --- a/Tests/Integration/UpdateServicesConfig.ps1 +++ b/Tests/Integration/UpdateServicesConfig.ps1 @@ -3,8 +3,8 @@ Configuration UpdateServicesConfig Import-DscResource -ModuleName UpdateServicesDsc Import-DscResource -ModuleName PSDesiredStateConfiguration - Node localhost - { + Node $AllNodes.NodeName + { WindowsFeature UpdateServices { @@ -40,6 +40,7 @@ Configuration UpdateServicesConfig ) SynchronizeAutomatically = $true SynchronizeAutomaticallyTimeOfDay = '15:30:00' + ClientTargetingMode = "Client" } UpdateServicesApprovalRule 'DefinitionUpdates' { @@ -58,7 +59,7 @@ Configuration UpdateServicesConfig Enabled = $true RunRuleNow = $true } - + UpdateServicesApprovalRule 'SecurityUpdates' { DependsOn = '[UpdateServicesServer]UpdateServices' @@ -67,7 +68,7 @@ Configuration UpdateServicesConfig Enabled = $true RunRuleNow = $true } - + UpdateServicesApprovalRule 'ServicePacks' { DependsOn = '[UpdateServicesServer]UpdateServices' @@ -94,7 +95,7 @@ Configuration UpdateServicesConfig DeclineSupersededUpdates = $true CleanupObsoleteUpdates = $true CleanupUnneededContentFiles = $true - } + } } }