From 720b0b5247a34aa07375b17fe3787909957af636 Mon Sep 17 00:00:00 2001 From: Katie Keim Date: Wed, 14 Dec 2016 18:56:20 -0800 Subject: [PATCH 1/4] Clean WOF Set and add tests --- .../MSFT_xWindowsOptionalFeature.psm1 | 8 +- .../xWindowsOptionalFeatureSet.psd1 | 88 +------ .../xWindowsOptionalFeatureSet.schema.psm1 | 42 +++- .../Sample_xWindowsOptionalFeatureSet.ps1 | 19 -- ...ple_xWindowsOptionalFeatureSet_Disable.ps1 | 16 ++ ...mple_xWindowsOptionalFeatureSet_Enable.ps1 | 17 ++ README.md | 53 +++-- ...MSFT_xWindowsFeature.Integration.Tests.ps1 | 224 ++++++++---------- ...wsOptionalFeatureSet.Integration.Tests.ps1 | 211 ++++++++++++----- .../xWindowsOptionalFeatureSet.config.ps1 | 36 +++ 10 files changed, 386 insertions(+), 328 deletions(-) delete mode 100644 Examples/Sample_xWindowsOptionalFeatureSet.ps1 create mode 100644 Examples/Sample_xWindowsOptionalFeatureSet_Disable.ps1 create mode 100644 Examples/Sample_xWindowsOptionalFeatureSet_Enable.ps1 create mode 100644 Tests/Integration/xWindowsOptionalFeatureSet.config.ps1 diff --git a/DSCResources/MSFT_xWindowsOptionalFeature/MSFT_xWindowsOptionalFeature.psm1 b/DSCResources/MSFT_xWindowsOptionalFeature/MSFT_xWindowsOptionalFeature.psm1 index 5ba5fb373..707c3f20e 100644 --- a/DSCResources/MSFT_xWindowsOptionalFeature/MSFT_xWindowsOptionalFeature.psm1 +++ b/DSCResources/MSFT_xWindowsOptionalFeature/MSFT_xWindowsOptionalFeature.psm1 @@ -127,11 +127,11 @@ function Set-TargetResource Assert-ResourcePrerequisitesValid - switch ($LogLevel) + $dismLogLevel = switch ($LogLevel) { - 'ErrorsOnly' { $dismLogLevel = 'Errors' } - 'ErrorsAndWarning' { $dismLogLevel = 'Warnings' } - 'ErrorsAndWarningAndInformation' { $dismLogLevel = 'WarningsInfo' } + 'ErrorsOnly' { 'Errors'; break } + 'ErrorsAndWarning' { 'Warnings'; break } + 'ErrorsAndWarningAndInformation' { 'WarningsInfo'; break } } # Construct splatting hashtable for DISM cmdlets diff --git a/DSCResources/xWindowsOptionalFeatureSet/xWindowsOptionalFeatureSet.psd1 b/DSCResources/xWindowsOptionalFeatureSet/xWindowsOptionalFeatureSet.psd1 index df311eef1..4a4942fc6 100644 --- a/DSCResources/xWindowsOptionalFeatureSet/xWindowsOptionalFeatureSet.psd1 +++ b/DSCResources/xWindowsOptionalFeatureSet/xWindowsOptionalFeatureSet.psd1 @@ -19,93 +19,9 @@ CompanyName = 'Microsoft Corporation' Copyright = '(c) 2016 Microsoft. All rights reserved.' # Description of the functionality provided by this module -Description = 'Allows installation and uninstallation of a group of xWindowsOptionalFeature resources with common parameters.' +Description = 'Provides a mechanism to configure and manage multiple xWindowsOptionalFeature resources on a target node.' # Minimum version of the Windows PowerShell engine required by this module -# PowerShellVersion = '' - -# Name of the Windows PowerShell host required by this module -# PowerShellHostName = '' - -# Minimum version of the Windows PowerShell host required by this module -# PowerShellHostVersion = '' - -# Minimum version of Microsoft .NET Framework required by this module -# DotNetFrameworkVersion = '' - -# Minimum version of the common language runtime (CLR) required by this module -# CLRVersion = '' - -# Processor architecture (None, X86, Amd64) required by this module -# ProcessorArchitecture = '' - -# Modules that must be imported into the global environment prior to importing this module -# RequiredModules = @() - -# Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() - -# Script files (.ps1) that are run in the caller's environment prior to importing this module. -# ScriptsToProcess = @() - -# Type files (.ps1xml) to be loaded when importing this module -# TypesToProcess = @() - -# Format files (.ps1xml) to be loaded when importing this module -# FormatsToProcess = @() - -# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess -# NestedModules = @() - -# Functions to export from this module -# FunctionsToExport = '*' - -# Cmdlets to export from this module -# CmdletsToExport = '*' - -# Variables to export from this module -# VariablesToExport = '*' - -# Aliases to export from this module -# AliasesToExport = '*' - -# DSC resources to export from this module -# DscResourcesToExport = 'xWindowsOptionalFeatureSet' <-- Will not parse in WMF 4 - -# List of all modules packaged with this module -# ModuleList = @() - -# List of all files packaged with this module -# FileList = @() - -# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. -PrivateData = @{ - - PSData = @{ - - # Tags applied to this module. These help with module discovery in online galleries. - # Tags = @() - - # A URL to the license for this module. - # LicenseUri = '' - - # A URL to the main website for this project. - # ProjectUri = '' - - # A URL to an icon representing this module. - # IconUri = '' - - # ReleaseNotes of this module - # ReleaseNotes = '' - - } # End of PSData hashtable - -} # End of PrivateData hashtable - -# HelpInfo URI of this module -# HelpInfoURI = '' - -# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. -# DefaultCommandPrefix = '' +PowerShellVersion = '4.0' } diff --git a/DSCResources/xWindowsOptionalFeatureSet/xWindowsOptionalFeatureSet.schema.psm1 b/DSCResources/xWindowsOptionalFeatureSet/xWindowsOptionalFeatureSet.schema.psm1 index 5013ef21f..6e1b65d6e 100644 --- a/DSCResources/xWindowsOptionalFeatureSet/xWindowsOptionalFeatureSet.schema.psm1 +++ b/DSCResources/xWindowsOptionalFeatureSet/xWindowsOptionalFeatureSet.schema.psm1 @@ -1,8 +1,38 @@ -Set-StrictMode -Version 'latest' -$errorActionPreference = 'stop' +$errorActionPreference = 'Stop' +Set-StrictMode -Version 'Latest' -Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'ResourceSetHelper.psm1') +# Import ResourceSetHelper for New-ResourceSetConfigurationScriptBlock +$script:dscResourcesFolderFilePath = Split-Path -Path $PSScriptRoot -Parent +$script:resourceSetHelperFilePath = Join-Path -Path $script:dscResourcesFolderFilePath -ChildPath 'ResourceSetHelper.psm1' +Import-Module -Name $script:resourceSetHelperFilePath +<# + .SYNOPSIS + A composite DSC resource to configure a set of similar xWindowsOptionalFeature resources. + + .PARAMETER Name + The names of the Windows optional features to enable or disable. + + .PARAMETER Ensure + Specifies whether the features should be enabled or disabled. + + To enable a set of features, set this property to Present. + To disable a set of features, set this property to Absent. + + .PARAMETER RemoveFilesOnDisable + Specifies whether or not to remove all files associated with the features when they are + disabled. + + .PARAMETER NoWindowsUpdateCheck + Specifies whether or not DISM should contact Windows Update (WU) when searching for the + source files to restore Windows optional features on an online image. + + .PARAMETER LogPath + The file path to which to log the opertation. + + .PARAMETER LogLevel + The level of detail to include in the log. +#> Configuration xWindowsOptionalFeatureSet { [CmdletBinding()] @@ -21,13 +51,13 @@ Configuration xWindowsOptionalFeatureSet [Boolean] $RemoveFilesOnDisable, + [Boolean] + $NoWindowsUpdateCheck, + [ValidateNotNullOrEmpty()] [String] $LogPath, - [Boolean] - $NoWindowsUpdateCheck, - [ValidateSet('ErrorsOnly', 'ErrorsAndWarning', 'ErrorsAndWarningAndInformation')] [String] $LogLevel diff --git a/Examples/Sample_xWindowsOptionalFeatureSet.ps1 b/Examples/Sample_xWindowsOptionalFeatureSet.ps1 deleted file mode 100644 index e6078815d..000000000 --- a/Examples/Sample_xWindowsOptionalFeatureSet.ps1 +++ /dev/null @@ -1,19 +0,0 @@ -# Installs the Windows optional features 'MicrosoftWindowsPowerShellV2' and 'Internet-Explorer-Optional-amd64' - -Configuration xWindowsOptionalFeatureSetExample -{ - param ( - [Parameter(Mandatory = $true)] - [String] - $LogPath - ) - - Import-DscResource -ModuleName xPSDesiredStateConfiguration - - xWindowsOptionalFeatureSet WindowsOptionalFeatureSet1 - { - Name = @('MicrosoftWindowsPowerShellV2', 'Internet-Explorer-Optional-amd64') - Ensure = 'Present' - LogPath = $LogPath - } -} diff --git a/Examples/Sample_xWindowsOptionalFeatureSet_Disable.ps1 b/Examples/Sample_xWindowsOptionalFeatureSet_Disable.ps1 new file mode 100644 index 000000000..777c5684f --- /dev/null +++ b/Examples/Sample_xWindowsOptionalFeatureSet_Disable.ps1 @@ -0,0 +1,16 @@ +<# + .SYNOPSIS + Disables the Windows optional features TelnetClient and LegacyComponents and removes all + files associated with these features. +#> +Configuration xWindowsOptionalFeatureSet_Disable +{ + Import-DscResource -ModuleName 'xPSDesiredStateConfiguration' + + xWindowsOptionalFeatureSet WindowsOptionalFeatureSet1 + { + Name = @('TelnetClient', 'LegacyComponents') + Ensure = 'Absent' + RemoveFilesOnDisable = $true + } +} diff --git a/Examples/Sample_xWindowsOptionalFeatureSet_Enable.ps1 b/Examples/Sample_xWindowsOptionalFeatureSet_Enable.ps1 new file mode 100644 index 000000000..1bb96b865 --- /dev/null +++ b/Examples/Sample_xWindowsOptionalFeatureSet_Enable.ps1 @@ -0,0 +1,17 @@ +<# + .SYNOPSIS + Enables the Windows optional features MicrosoftWindowsPowerShellV2 and + Internet-Explorer-Optional-amd64 and outputs a log of the operations to a file at the path + 'C:\LogPath\Log.txt'. +#> +Configuration xWindowsOptionalFeatureSet_Enable +{ + Import-DscResource -ModuleName 'xPSDesiredStateConfiguration' + + xWindowsOptionalFeatureSet WindowsOptionalFeatureSet1 + { + Name = @('MicrosoftWindowsPowerShellV2', 'Internet-Explorer-Optional-amd64') + Ensure = 'Present' + LogPath = 'C:\LogPath\Log.txt' + } +} diff --git a/README.md b/README.md index eb9b6d6d9..daae08446 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Please check out common DSC Resources [contributing guidelines](https://github.c * **xWindowsFeature** provides a mechanism to install or uninstall Windows roles or features on a target node. * **xWindowsFeatureSet** allows installation and uninstallation of a group of Windows features and their subfeatures. * **xWindowsOptionalFeature** provides a mechanism to enable or disable optional features on a target node. -* **xWindowsOptionalFeatureSet** allows installation and uninstallation of a group of optional Windows features. +* **xWindowsOptionalFeatureSet** provides a mechanism to configure and manage multiple xWindowsOptionalFeature resources on a target node. * **xWindowsPackageCab** provides a mechanism to install or uninstall a package from a Windows cabinet (cab) file on a target node. Resources that work on Nano Server: @@ -45,9 +45,9 @@ Resources that work on Nano Server: * xScript * xUser * xWindowsOptionalFeature +* xWindowsOptionalFeatureSet * xWindowsPackageCab - ### xArchive * **Destination**: (Key) Specifies the location where you want to ensure the archive contents are extracted. @@ -439,28 +439,34 @@ This resource works on Nano Server. * [Enable the specified windows optional feature and output logs to the specified path](https://github.com/PowerShell/xPSDesiredStateConfiguration/blob/dev/Examples/Sample_xWindowsOptionalFeature.ps1) ### xWindowsOptionalFeatureSet -Note: xWindowsOptionalFeature is only supported on Windows client or Windows Server 2012 (and later) SKUs. +Provides a mechanism to configure and manage multiple xWindowsOptionalFeature resources on a target node. +This resource works on Nano Server. -* **Name**: Defines the names of the Windows optional features in the set. +#### Requirements -These parameters will be the same for each Windows optional feature in the set. Please refer to the xWindowsOptionalFeature section above for more details on these parameters: -* **Ensure**: Ensures that the set of features is present or absent. - - Supported values: Present, Absent. - - Default Value: Present. -* **Source**: Specifies the location of the files that are required to restore a feature that has been removed from the image. - - You can specify the Windows directory of a mounted image or a running Windows installation that is shared on the network. - - If you specify multiple Source arguments, the files are gathered from the first location where they are found and the rest of the locations are ignored. -* **RemoveFilesOnDisable**: Removes the files for an optional feature without removing the feature's manifest from the image. - - Suported values: $true, $false. - - Default value: $false. -* **LogPath**: Specifies the full path and file name to log to. - - If not set, the default is %WINDIR%\Logs\Dism\dism.log. -* **NoWindowsUpdateCheck**: Prevents DISM from contacting Windows Update (WU) when searching for the source files to restore a feature on an online image. - - Suported values: $true, $false. - - Default value: $false. -* **LogLevel**: Specifies the maximum output level shown in the logs. - - Suported values: ErrorsOnly, ErrorsAndWarning, ErrorsAndWarningAndInformation. - - Default value: ErrorsOnly. +* Target machine must be running a Windows client operating system, Windows Server 2012 or later, or Nano Server. +* Target machine must have access to the DISM PowerShell module. + +#### Parameters + +* **[String[]] Name** _(Key)_: The names of the Windows optional features to enable or disable. + +The following parameters will be the same for each Windows optional feature in the set: + +* **[String] Ensure** _(Write)_: Specifies whether the Windows optional features should be enabled or disabled. To enable the features, set this property to Present. To disable the features, set this property to Absent. { *Present* | Absent }. +* **[Boolean] RemoveFilesOnDisable** _(Write)_: Specifies whether or not to remove the files associated with the Windows optional features when they are disabled. +* **[Boolean] NoWindowsUpdateCheck** _(Write)_: Specifies whether or not DISM should contact Windows Update (WU) when searching for the source files to restore Windows optional features on an online image. +* **[String] LogPath** _(Write)_: The file path to which to log the operation. +* **[String] LogLevel** _(Write)_: The level of detail to include in the log. { ErrorsOnly | ErrorsAndWarning | ErrorsAndWarningAndInformation }. + +#### Read-Only Properties from Get-TargetResource + +None + +#### Examples + +* [Enable multiple features](https://github.com/PowerShell/xPSDesiredStateConfiguration/blob/dev/Examples/Sample_xWindowsOptionalFeatureSet_Enable.ps1) +* [Disable multiple features](https://github.com/PowerShell/xPSDesiredStateConfiguration/blob/dev/Examples/Sample_xWindowsOptionalFeatureSet_Disable.ps1) ### xWindowsPackageCab Provides a mechanism to install or uninstall a package from a windows cabinet (cab) file on a target node. @@ -539,6 +545,9 @@ None * Added a 'Count' value to the hashtable returned by Get-TargetResource so that the user can see how many instances of the process are running. * Fixed bug in finding the path to the executable. * Changed name to be xWindowsProcess everywhere. +* xWindowsOptionalFeatureSet + * Updated resource to use new ResouceSetHelper functions and added integration tests. + * Updated documentation and example ### 5.0.0.0 diff --git a/Tests/Integration/MSFT_xWindowsFeature.Integration.Tests.ps1 b/Tests/Integration/MSFT_xWindowsFeature.Integration.Tests.ps1 index ef5d8df42..5f29af422 100644 --- a/Tests/Integration/MSFT_xWindowsFeature.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_xWindowsFeature.Integration.Tests.ps1 @@ -19,12 +19,6 @@ $script:testEnvironment = Enter-DscResourceTestEnvironment ` -DscResourceModuleName 'xPSDesiredStateConfiguration' ` -DscResourceName 'MSFT_xWindowsFeature' ` -TestType 'Integration' - -$script:testFeatureName = 'Telnet-Client' -$script:testFeatureWithSubFeaturesName = 'ADRMS' -$script:installStateOfTestFeature -$script:installStateOfTestWithSubFeatures - <# If this is set to $true then the tests that test installing/uninstalling a feature with its subfeatures will not run. @@ -32,21 +26,48 @@ $script:installStateOfTestWithSubFeatures $script:skipLongTests = $false try { + Describe 'xWindowsFeature Integration Tests' { + BeforeAll { + $script:testFeatureName = 'Telnet-Client' + $script:testFeatureWithSubFeaturesName = 'RSAT-File-Services' - #Saving the state so we can clean up afterwards - $testFeature = Get-WindowsFeature -Name $script:testFeatureName - $script:installStateOfTestFeature = $testFeature.Installed + #Saving the state so we can clean up afterwards + $testFeature = Get-WindowsFeature -Name $script:testFeatureName + $script:installStateOfTestFeature = $testFeature.Installed - $testFeatureWithSubFeatures = Get-WindowsFeature -Name $script:testFeatureWithSubFeaturesName - $script:installStateOfTestWithSubFeatures = $testFeatureWithSubFeatures.Installed + $testFeatureWithSubFeatures = Get-WindowsFeature -Name $script:testFeatureWithSubFeaturesName + $script:installStateOfTestWithSubFeatures = $testFeatureWithSubFeatures.Installed - $configFile = Join-Path -Path $PSScriptRoot -ChildPath 'MSFT_xWindowsFeature.config.ps1' + $configFile = Join-Path -Path $PSScriptRoot -ChildPath 'MSFT_xWindowsFeature.config.ps1' + } - Describe 'xWindowsFeature Integration Tests' { + AfterAll { + # Ensure that features used for testing are re-installed/uninstalled + $feature = Get-WindowsFeature -Name $script:testFeatureName - $testIncludeAllSubFeature = $false + if ($script:installStateOfTestFeature -and -not $feature.Installed) + { + Add-WindowsFeature -Name $script:testFeatureName + } + elseif ( -not $script:installStateOfTestFeature -and $feature.Installed) + { + Remove-WindowsFeature -Name $script:testFeatureName + } - Remove-WindowsFeature -Name $script:testFeatureName + if (-not $script:skipLongTests) + { + $feature = Get-WindowsFeature -Name $script:testFeatureWithSubFeaturesName + + if ($script:installStateOfTestWithSubFeatures -and -not $feature.Installed) + { + Add-WindowsFeature -Name $script:testFeatureWithSubFeaturesName -IncludeAllSubFeature + } + elseif ( -not $script:installStateOfTestWithSubFeatures -and $feature.Installed) + { + Remove-WindowsFeature -Name $script:testFeatureWithSubFeaturesName + } + } + } Context "Should Install the Windows Feature: $script:testFeatureName" { $configurationName = 'MSFT_xWindowsFeature_InstallFeature' @@ -63,7 +84,7 @@ try { { . $configFile -ConfigurationName $configurationName & $configurationName -Name $script:testFeatureName ` - -IncludeAllSubFeature $testIncludeAllSubFeature ` + -IncludeAllSubFeature $false ` -Ensure 'Present' ` -OutputPath $configurationPath ` -ErrorAction 'Stop' @@ -78,7 +99,7 @@ try { It 'Should return the correct configuration' { $currentConfig = Get-DscConfiguration -ErrorAction 'Stop' $currentConfig.Name | Should Be $script:testFeatureName - $currentConfig.IncludeAllSubFeature | Should Be $testIncludeAllSubFeature + $currentConfig.IncludeAllSubFeature | Should Be $false $currentConfig.Ensure | Should Be 'Present' } @@ -112,7 +133,7 @@ try { { . $configFile -ConfigurationName $configurationName & $configurationName -Name $script:testFeatureName ` - -IncludeAllSubFeature $testIncludeAllSubFeature ` + -IncludeAllSubFeature $false ` -Ensure 'Absent' ` -OutputPath $configurationPath ` -ErrorAction 'Stop' @@ -127,7 +148,7 @@ try { It 'Should return the correct configuration' { $currentConfig = Get-DscConfiguration -ErrorAction 'Stop' $currentConfig.Name | Should Be $script:testFeatureName - $currentConfig.IncludeAllSubFeature | Should Be $testIncludeAllSubFeature + $currentConfig.IncludeAllSubFeature | Should Be $false $currentConfig.Ensure | Should Be 'Absent' } @@ -155,58 +176,43 @@ try { $logPath = Join-Path -Path $TestDrive -ChildPath 'InstallSubFeatureTest.log' - try + if (-not $script:skipLongTests) { - if (-not $script:skipLongTests) - { - # Ensure that the feature is not already installed - Remove-WindowsFeature -Name $script:testFeatureWithSubFeaturesName - } + # Ensure that the feature is not already installed + Remove-WindowsFeature -Name $script:testFeatureWithSubFeaturesName + } - It 'Should compile without throwing' -Skip:$script:skipLongTests { - { - . $configFile -ConfigurationName $configurationName - & $configurationName -Name $script:testFeatureWithSubFeaturesName ` - -IncludeAllSubFeature $true ` - -Ensure 'Present' ` - -OutputPath $configurationPath ` - -ErrorAction 'Stop' - Start-DscConfiguration -Path $configurationPath -ErrorAction 'Stop' -Wait -Force - } | Should Not Throw - } + It 'Should compile without throwing' -Skip:$script:skipLongTests { + { + . $configFile -ConfigurationName $configurationName + & $configurationName -Name $script:testFeatureWithSubFeaturesName ` + -IncludeAllSubFeature $true ` + -Ensure 'Present' ` + -OutputPath $configurationPath ` + -ErrorAction 'Stop' + Start-DscConfiguration -Path $configurationPath -ErrorAction 'Stop' -Wait -Force + } | Should Not Throw + } - It 'Should be able to call Get-DscConfiguration without throwing' -Skip:$script:skipLongTests { - { Get-DscConfiguration -ErrorAction 'Stop' } | Should Not Throw - } + It 'Should be able to call Get-DscConfiguration without throwing' -Skip:$script:skipLongTests { + { Get-DscConfiguration -ErrorAction 'Stop' } | Should Not Throw + } - It 'Should return the correct configuration' -Skip:$script:skipLongTests { - $currentConfig = Get-DscConfiguration -ErrorAction 'Stop' - $currentConfig.Name | Should Be $script:testFeatureWithSubFeaturesName - $currentConfig.IncludeAllSubFeature | Should Be $true - $currentConfig.Ensure | Should Be 'Present' - } - - It 'Should be Installed (includes check for subFeatures)' -Skip:$script:skipLongTests { - $feature = Get-WindowsFeature -Name $script:testFeatureWithSubFeaturesName - $feature.Installed | Should Be $true - - foreach ($subFeatureName in $feature.SubFeatures) - { - $subFeature = Get-WindowsFeature -Name $subFeatureName - $subFeature.Installed | Should Be $true - } - } - + It 'Should return the correct configuration' -Skip:$script:skipLongTests { + $currentConfig = Get-DscConfiguration -ErrorAction 'Stop' + $currentConfig.Name | Should Be $script:testFeatureWithSubFeaturesName + $currentConfig.IncludeAllSubFeature | Should Be $true + $currentConfig.Ensure | Should Be 'Present' } - finally - { - if (Test-Path -Path $logPath) { - Remove-Item -Path $logPath -Recurse -Force - } - if (Test-Path -Path $configurationPath) + It 'Should be Installed (includes check for subFeatures)' -Skip:$script:skipLongTests { + $feature = Get-WindowsFeature -Name $script:testFeatureWithSubFeaturesName + $feature.Installed | Should Be $true + + foreach ($subFeatureName in $feature.SubFeatures) { - Remove-Item -Path $configurationPath -Recurse -Force + $subFeature = Get-WindowsFeature -Name $subFeatureName + $subFeature.Installed | Should Be $true } } } @@ -217,52 +223,37 @@ try { $logPath = Join-Path -Path $TestDrive -ChildPath 'UninstallSubFeatureTest.log' - try - { - It 'Should compile without throwing' -Skip:$script:skipLongTests { - { - . $configFile -ConfigurationName $configurationName - & $configurationName -Name $script:testFeatureWithSubFeaturesName ` - -IncludeAllSubFeature $true ` - -Ensure 'Absent' ` - -OutputPath $configurationPath ` - -ErrorAction 'Stop' - Start-DscConfiguration -Path $configurationPath -ErrorAction 'Stop' -Wait -Force - } | Should Not Throw - } + It 'Should compile without throwing' -Skip:$script:skipLongTests { + { + . $configFile -ConfigurationName $configurationName + & $configurationName -Name $script:testFeatureWithSubFeaturesName ` + -IncludeAllSubFeature $true ` + -Ensure 'Absent' ` + -OutputPath $configurationPath ` + -ErrorAction 'Stop' + Start-DscConfiguration -Path $configurationPath -ErrorAction 'Stop' -Wait -Force + } | Should Not Throw + } - It 'Should be able to call Get-DscConfiguration without throwing' -Skip:$script:skipLongTests { - { Get-DscConfiguration -ErrorAction 'Stop' } | Should Not Throw - } + It 'Should be able to call Get-DscConfiguration without throwing' -Skip:$script:skipLongTests { + { Get-DscConfiguration -ErrorAction 'Stop' } | Should Not Throw + } - It 'Should return the correct configuration' -Skip:$script:skipLongTests { - $currentConfig = Get-DscConfiguration -ErrorAction 'Stop' - $currentConfig.Name | Should Be $script:testFeatureWithSubFeaturesName - $currentConfig.IncludeAllSubFeature | Should Be $false - $currentConfig.Ensure | Should Be 'Absent' - } - - It 'Should not be installed (includes check for subFeatures)' -Skip:$script:skipLongTests { - $feature = Get-WindowsFeature -Name $script:testFeatureWithSubFeaturesName - $feature.Installed | Should Be $false - - foreach ($subFeatureName in $feature.SubFeatures) - { - $subFeature = Get-WindowsFeature -Name $subFeatureName - $subFeature.Installed | Should Be $false - } - } - + It 'Should return the correct configuration' -Skip:$script:skipLongTests { + $currentConfig = Get-DscConfiguration -ErrorAction 'Stop' + $currentConfig.Name | Should Be $script:testFeatureWithSubFeaturesName + $currentConfig.IncludeAllSubFeature | Should Be $false + $currentConfig.Ensure | Should Be 'Absent' } - finally - { - if (Test-Path -Path $logPath) { - Remove-Item -Path $logPath -Recurse -Force - } - if (Test-Path -Path $configurationPath) + It 'Should not be installed (includes check for subFeatures)' -Skip:$script:skipLongTests { + $feature = Get-WindowsFeature -Name $script:testFeatureWithSubFeaturesName + $feature.Installed | Should Be $false + + foreach ($subFeatureName in $feature.SubFeatures) { - Remove-Item -Path $configurationPath -Recurse -Force + $subFeature = Get-WindowsFeature -Name $subFeatureName + $subFeature.Installed | Should Be $false } } } @@ -270,28 +261,5 @@ try { } finally { - # Ensure that features used for testing are re-installed/uninstalled - if ($script:installStateOfTestFeature) - { - Add-WindowsFeature -Name $script:testFeatureName - } - else - { - Remove-WindowsFeature -Name $script:testFeatureName - } - - if (-not $script:skipLongTests) - { - if ($script:installStateOfTestWithSubFeatures) - { - Add-WindowsFeature -Name $script:testFeatureWithSubFeaturesName -IncludeAllSubFeature - } - else - { - Remove-WindowsFeature -Name $script:testFeatureWithSubFeaturesName - } - } Exit-DscResourceTestEnvironment -TestEnvironment $script:testEnvironment } - - diff --git a/Tests/Integration/xWindowsOptionalFeatureSet.Integration.Tests.ps1 b/Tests/Integration/xWindowsOptionalFeatureSet.Integration.Tests.ps1 index ef7e4d5d9..dabb768b9 100644 --- a/Tests/Integration/xWindowsOptionalFeatureSet.Integration.Tests.ps1 +++ b/Tests/Integration/xWindowsOptionalFeatureSet.Integration.Tests.ps1 @@ -1,7 +1,10 @@ -Set-StrictMode -Version 'latest' -$errorActionPreference = 'stop' +$errorActionPreference = 'Stop' +Set-StrictMode -Version 'Latest' -Import-Module -Name (Join-Path -Path (Split-Path $PSScriptRoot -Parent) -ChildPath 'CommonTestHelper.psm1') +# Import CommonTestHelper for Enter-DscResourceTestEnvironment, Exit-DscResourceTestEnvironment +$script:testsFolderFilePath = Split-Path $PSScriptRoot -Parent +$script:commonTestHelperFilePath = Join-Path -Path $testsFolderFilePath -ChildPath 'CommonTestHelper.psm1' +Import-Module -Name $commonTestHelperFilePath $script:testEnvironment = Enter-DscResourceTestEnvironment ` -DscResourceModuleName 'xPSDesiredStateConfiguration' ` @@ -10,107 +13,189 @@ $script:testEnvironment = Enter-DscResourceTestEnvironment ` try { - Describe "xWindowsOptionalFeatureSet Integration Tests" { - It "Should install two valid Windows optional features" { - $configurationName = "InstallOptionalFeature" - $configurationPath = Join-Path -Path $TestDrive -ChildPath $configurationName - $logPath = Join-Path -Path $TestDrive -ChildPath 'InstallOptionalFeature.log' + Describe 'xWindowsOptionalFeatureSet Integration Tests' { + BeforeAll { + $script:confgurationFilePath = Join-Path -Path $PSScriptRoot -ChildPath 'xWindowsOptionalFeatureSet.config.ps1' - $validFeatureName1 = 'MicrosoftWindowsPowerShellV2' - $validFeatureName2 = 'Internet-Explorer-Optional-amd64' + $script:enabledStates = @( 'Enabled', 'EnablePending' ) + $script:disabledStates = @( 'Disabled', 'DisablePending' ) - $originalFeature1 = Dism\Get-WindowsOptionalFeature -Online -FeatureName $validFeatureName1 - $originalFeature2 = Dism\Get-WindowsOptionalFeature -Online -FeatureName $validFeatureName2 + $script:validFeatureNames = @( 'RSAT-RDS-Tools-Feature', 'Xps-Foundation-Xps-Viewer' ) - try + $script:originalFeatures = @{} + + foreach ($validFeatureName in $script:validFeatureNames) { - Configuration $configurationName - { - Import-DscResource -ModuleName 'xPSDesiredStateConfiguration' + $script:originalFeatures[$validFeatureName] = Dism\Get-WindowsOptionalFeature -FeatureName $validFeatureName -Online + } + } + + AfterAll { + foreach ($validFeatureName in $script:originalFeatures.Keys) + { + $originalFeature = $script:originalFeatures[$validFeatureName] - xWindowsOptionalFeatureSet xWindowsOptionalFeatureSet1 + if ($null -ne $originalFeature) + { + if ($originalFeature.State -in $script:disabledStates) + { + Dism\Disable-WindowsOptionalFeature -Online -FeatureName $validFeatureName -NoRestart + } + elseif ($originalFeature.State -in $script:enabledStates) { - Name = @($validFeatureName1, $validFeatureName2) - Ensure = 'Present' - LogPath = $logPath - NoWindowsUpdateCheck = $true + Dism\Enable-WindowsOptionalFeature -Online -FeatureName $windowsOptionalFeatureName -NoRestart } } + } + } - { & $configurationName -OutputPath $configurationPath } | Should Not Throw + Context 'Install two valid Windows optional features' { + $configurationName = 'InstallOptionalFeature' - { Start-DscConfiguration -Path $configurationPath -Wait -Force -Verbose } | Should Not Throw + $wofSetParameters = @{ + WindowsOptionalFeatureNames = $script:validFeatureNames + Ensure = 'Present' + LogPath = Join-Path -Path $TestDrive -ChildPath 'InstallOptionalFeature.log' + } + + foreach ($windowsOptionalFeatureName in $wofSetParameters.WindowsOptionalFeatureNames) + { + $windowsOptionalFeature = Dism\Get-WindowsOptionalFeature -Online -FeatureName $windowsOptionalFeatureName - $windowsOptionalFeature1 = Dism\Get-WindowsOptionalFeature -Online -FeatureName $validFeatureName1 + It "Should be able to retrieve Windows optional feature $windowsOptionalFeatureName before the configuration" { + $windowsOptionalFeature | Should Not Be $null + } - $windowsOptionalFeature1 | Should Not Be $null - $windowsOptionalFeature1.State -eq 'Enabled' -or $windowsOptionalFeature1.State -eq 'EnablePending' | Should Be $true + if ($windowsOptionalFeature.State -in $script:enabledStates) + { + Dism\Disable-WindowsOptionalFeature -Online -FeatureName $windowsOptionalFeatureName -NoRestart + $windowsOptionalFeature = Dism\Get-WindowsOptionalFeature -Online -FeatureName $windowsOptionalFeatureName - $windowsOptionalFeature2 = Dism\Get-WindowsOptionalFeature -Online -FeatureName $validFeatureName2 + # May need to wait a moment for the correct state to populate + $millisecondsElapsed = 0 + $startTime = Get-Date + while (-not ($windowsOptionalFeature.State -in $script:disabledStates) -and $millisecondsElapsed -lt 3000) + { + $windowsOptionalFeature = Dism\Get-WindowsOptionalFeature -Online -FeatureName $windowsOptionalFeatureName + $millisecondsElapsed = ((Get-Date) - $startTime).TotalMilliseconds + } + } + + It "Should have disabled Windows optional feature $windowsOptionalFeatureName before the configuration" { + $windowsOptionalFeature.State -in $script:disabledStates | Should Be $true + } + } - $windowsOptionalFeature2 | Should Not Be $null - $windowsOptionalFeature2.State -eq 'Enabled' -or $windowsOptionalFeature2.State -eq 'EnablePending' | Should Be $true + It 'Should compile and run configuration' { + { + . $script:confgurationFilePath -ConfigurationName $configurationName + & $configurationName -OutputPath $TestDrive @wofSetParameters + Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force + } | Should Not Throw } - finally + + foreach ($windowsOptionalFeatureName in $wofSetParameters.WindowsOptionalFeatureNames) { - if ($originalFeature1.State -eq 'Disabled' -or $originalFeature1.State -eq 'DisablePending') - { - Dism\Disable-WindowsOptionalFeature -Online -FeatureName $validFeatureName1 -NoRestart + $windowsOptionalFeature = Dism\Get-WindowsOptionalFeature -Online -FeatureName $windowsOptionalFeatureName + It "Should be able to retrieve Windows optional feature $windowsOptionalFeatureName after the configuration" { + $windowsOptionalFeature | Should Not Be $null } - if ($originalFeature2.State -eq 'Disabled' -or $originalFeature2.State -eq 'DisablePending') + # May need to wait a moment for the correct state to populate + $millisecondsElapsed = 0 + $startTime = Get-Date + while (-not ($windowsOptionalFeature.State -in $script:enabledStates) -and $millisecondsElapsed -lt 3000) { - Dism\Disable-WindowsOptionalFeature -Online -FeatureName $validFeatureName2 -NoRestart + $windowsOptionalFeature = Dism\Get-WindowsOptionalFeature -Online -FeatureName $windowsOptionalFeatureName + $millisecondsElapsed = ((Get-Date) - $startTime).TotalMilliseconds } - if (Test-Path -Path $logPath) { - Remove-Item -Path $logPath -Recurse -Force + It "Should have enabled Windows optional feature $windowsOptionalFeatureName after the configuration" { + $windowsOptionalFeature.State -in $script:enabledStates | Should Be $true } + } + + It 'Should have created the log file' { + Test-Path -Path $wofSetParameters.LogPath | Should Be $true + } - if (Test-Path -Path $configurationPath) - { - Remove-Item -Path $configurationPath -Recurse -Force - } + It 'Should have created content in the log file' { + Get-Content -Path $wofSetParameters.LogPath -Raw | Should Not Be $null } } - It "Should not install an incorrect Windows optional feature" { - $configurationName = "InstallIncorrectWindowsFeature" - $configurationPath = Join-Path -Path $TestDrive -ChildPath $configurationName - $logPath = Join-Path -Path $TestDrive -ChildPath 'InstallIncorrectWindowsFeature.log' + Context 'Uninstall two valid Windows optional features' { + $configurationName = 'UninstallOptionalFeature' - try + $wofSetParameters = @{ + WindowsOptionalFeatureNames = $script:validFeatureNames + Ensure = 'Absent' + LogPath = Join-Path -Path $TestDrive -ChildPath 'UninstallOptionalFeature.log' + } + + foreach ($windowsOptionalFeatureName in $wofSetParameters.WindowsOptionalFeatureNames) { - Configuration $configurationName + $windowsOptionalFeature = Dism\Get-WindowsOptionalFeature -Online -FeatureName $windowsOptionalFeatureName + + It "Should be able to retrieve Windows optional feature $windowsOptionalFeatureName before the configuration" { + $windowsOptionalFeature | Should Not Be $null + } + + if ($windowsOptionalFeature.State -in $script:disabledStates) { - Import-DscResource -ModuleName xPSDesiredStateConfiguration + Dism\Enable-WindowsOptionalFeature -Online -FeatureName $windowsOptionalFeatureName -NoRestart + $windowsOptionalFeature = Dism\Get-WindowsOptionalFeature -Online -FeatureName $windowsOptionalFeatureName - xWindowsOptionalFeatureSet feature1 + # May need to wait a moment for the correct state to populate + $millisecondsElapsed = 0 + $startTime = Get-Date + while (-not ($windowsOptionalFeature.State -in $script:enabledStates) -and $millisecondsElapsed -lt 3000) { - Name = @("NonExistentWindowsOptionalFeature") - Ensure = "Present" - LogPath = $logPath + $windowsOptionalFeature = Dism\Get-WindowsOptionalFeature -Online -FeatureName $windowsOptionalFeatureName + $millisecondsElapsed = ((Get-Date) - $startTime).TotalMilliseconds } } - { & $configurationName -OutputPath $configurationPath } | Should Not Throw - - # This should not work. LCM is expected to print errors, but the call to this function itself should not throw errors. - { Start-DscConfiguration -Path $configurationPath -Wait -Force -ErrorAction SilentlyContinue } | Should Not Throw + It "Should have enabled Windows optional feature $windowsOptionalFeatureName before the configuration" { + $windowsOptionalFeature.State -in $script:enabledStates | Should Be $true + } + } - Test-Path -Path $logPath | Should Be $true + It 'Should compile and run configuration' { + { + . $script:confgurationFilePath -ConfigurationName $configurationName + & $configurationName -OutputPath $TestDrive @wofSetParameters + Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force + } | Should Not Throw } - finally + + foreach ($windowsOptionalFeatureName in $wofSetParameters.WindowsOptionalFeatureNames) { - if (Test-Path -Path $logPath) - { - Remove-Item -Path $logPath -Recurse -Force + $windowsOptionalFeature = Dism\Get-WindowsOptionalFeature -Online -FeatureName $windowsOptionalFeatureName + It "Should be able to retrieve Windows optional feature $windowsOptionalFeatureName after the confguration" { + $windowsOptionalFeature | Should Not Be $null } - if (Test-Path -Path $configurationPath) + # May need to wait a moment for the correct state to populate + $millisecondsElapsed = 0 + $startTime = Get-Date + while (-not ($windowsOptionalFeature.State -in $script:disabledStates) -and $millisecondsElapsed -lt 3000) { - Remove-Item -Path $configurationPath -Recurse -Force + $windowsOptionalFeature = Dism\Get-WindowsOptionalFeature -Online -FeatureName $windowsOptionalFeatureName + $millisecondsElapsed = ((Get-Date) - $startTime).TotalMilliseconds } + + It "Should have disabled Windows optional feature $windowsOptionalFeatureName after the confguration" { + $windowsOptionalFeature.State -in $script:disabledStates | Should Be $true + } + } + + It 'Should have created the log file' { + Test-Path -Path $wofSetParameters.LogPath | Should Be $true + } + + It 'Should have created content in the log file' { + Get-Content -Path $wofSetParameters.LogPath -Raw | Should Not Be $null } } } diff --git a/Tests/Integration/xWindowsOptionalFeatureSet.config.ps1 b/Tests/Integration/xWindowsOptionalFeatureSet.config.ps1 new file mode 100644 index 000000000..9605a5ef1 --- /dev/null +++ b/Tests/Integration/xWindowsOptionalFeatureSet.config.ps1 @@ -0,0 +1,36 @@ +param +( + [Parameter(Mandatory = $true)] + [String] + $ConfigurationName +) + +Configuration $ConfigurationName +{ + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String[]] + $WindowsOptionalFeatureNames, + + [ValidateSet('Present', 'Absent')] + [String] + $Ensure = 'Present', + + [ValidateNotNullOrEmpty()] + [String] + $LogPath + ) + + Import-DscResource -ModuleName 'xPSDesiredStateConfiguration' + + xWindowsOptionalFeatureSet xWindowsOptionalFeatureSet1 + { + Name = $WindowsOptionalFeatureNames + Ensure = $Ensure + LogPath = $LogPath + NoWindowsUpdateCheck = $false + RemoveFilesOnDisable = $false + } +} From 5745964ab179725aa2d4e7139976312e2fbb5073 Mon Sep 17 00:00:00 2001 From: Katie Keim Date: Thu, 15 Dec 2016 13:48:50 -0800 Subject: [PATCH 2/4] Clean WindowsFeatureSet and add tests --- .../MSFT_xWindowsFeature.psm1 | 2 +- .../xWindowsFeatureSet.psd1 | 88 +------- .../xWindowsFeatureSet.schema.psm1 | 37 ++- Examples/Sample_xWindowsFeatureSet.ps1 | 31 --- .../Sample_xWindowsFeatureSet_Install.ps1 | 20 ++ .../Sample_xWindowsFeatureSet_Uninstall.ps1 | 20 ++ README.md | 43 +++- ...MSFT_xWindowsFeature.Integration.Tests.ps1 | 9 +- .../xWindowsFeatureSet.Integration.Tests.ps1 | 211 +++++++++++++++--- .../Integration/xWindowsFeatureSet.config.ps1 | 35 +++ 10 files changed, 330 insertions(+), 166 deletions(-) delete mode 100644 Examples/Sample_xWindowsFeatureSet.ps1 create mode 100644 Examples/Sample_xWindowsFeatureSet_Install.ps1 create mode 100644 Examples/Sample_xWindowsFeatureSet_Uninstall.ps1 create mode 100644 Tests/Integration/xWindowsFeatureSet.config.ps1 diff --git a/DSCResources/MSFT_xWindowsFeature/MSFT_xWindowsFeature.psm1 b/DSCResources/MSFT_xWindowsFeature/MSFT_xWindowsFeature.psm1 index 06d1254d8..41074694d 100644 --- a/DSCResources/MSFT_xWindowsFeature/MSFT_xWindowsFeature.psm1 +++ b/DSCResources/MSFT_xWindowsFeature/MSFT_xWindowsFeature.psm1 @@ -158,7 +158,7 @@ function Get-TargetResource #> function Set-TargetResource { - [CmdletBinding(SupportsShouldProcess = $true)] + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] diff --git a/DSCResources/xWindowsFeatureSet/xWindowsFeatureSet.psd1 b/DSCResources/xWindowsFeatureSet/xWindowsFeatureSet.psd1 index 3d37c4aba..6849d9943 100644 --- a/DSCResources/xWindowsFeatureSet/xWindowsFeatureSet.psd1 +++ b/DSCResources/xWindowsFeatureSet/xWindowsFeatureSet.psd1 @@ -19,93 +19,9 @@ CompanyName = 'Microsoft Corporation' Copyright = '(c) 2016 Microsoft. All rights reserved.' # Description of the functionality provided by this module -Description = 'Allows installation and uninstallation of a group of windows features and their subfeatures.' +Description = 'Provides a mechanism to configure and manage multiple xWindowsFeature resources on a target node.' # Minimum version of the Windows PowerShell engine required by this module -# PowerShellVersion = '' - -# Name of the Windows PowerShell host required by this module -# PowerShellHostName = '' - -# Minimum version of the Windows PowerShell host required by this module -# PowerShellHostVersion = '' - -# Minimum version of Microsoft .NET Framework required by this module -# DotNetFrameworkVersion = '' - -# Minimum version of the common language runtime (CLR) required by this module -# CLRVersion = '' - -# Processor architecture (None, X86, Amd64) required by this module -# ProcessorArchitecture = '' - -# Modules that must be imported into the global environment prior to importing this module -# RequiredModules = @() - -# Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() - -# Script files (.ps1) that are run in the caller's environment prior to importing this module. -# ScriptsToProcess = @() - -# Type files (.ps1xml) to be loaded when importing this module -# TypesToProcess = @() - -# Format files (.ps1xml) to be loaded when importing this module -# FormatsToProcess = @() - -# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess -# NestedModules = @() - -# Functions to export from this module -# FunctionsToExport = '*' - -# Cmdlets to export from this module -# CmdletsToExport = '*' - -# Variables to export from this module -# VariablesToExport = '*' - -# Aliases to export from this module -# AliasesToExport = '*' - -# DSC resources to export from this module -# DscResourcesToExport = '*' - -# List of all modules packaged with this module -# ModuleList = @() - -# List of all files packaged with this module -# FileList = @() - -# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. -PrivateData = @{ - - PSData = @{ - - # Tags applied to this module. These help with module discovery in online galleries. - # Tags = @() - - # A URL to the license for this module. - # LicenseUri = '' - - # A URL to the main website for this project. - # ProjectUri = '' - - # A URL to an icon representing this module. - # IconUri = '' - - # ReleaseNotes of this module - # ReleaseNotes = '' - - } # End of PSData hashtable - -} # End of PrivateData hashtable - -# HelpInfo URI of this module -# HelpInfoURI = '' - -# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. -# DefaultCommandPrefix = '' +PowerShellVersion = '4.0' } diff --git a/DSCResources/xWindowsFeatureSet/xWindowsFeatureSet.schema.psm1 b/DSCResources/xWindowsFeatureSet/xWindowsFeatureSet.schema.psm1 index 8d59f54e9..c91f84112 100644 --- a/DSCResources/xWindowsFeatureSet/xWindowsFeatureSet.schema.psm1 +++ b/DSCResources/xWindowsFeatureSet/xWindowsFeatureSet.schema.psm1 @@ -1,8 +1,38 @@ -Set-StrictMode -Version 'latest' -$errorActionPreference = 'stop' +$errorActionPreference = 'Stop' +Set-StrictMode -Version 'Latest' -Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'ResourceSetHelper.psm1') +# Import ResourceSetHelper for New-ResourceSetConfigurationScriptBlock +$script:dscResourcesFolderFilePath = Split-Path -Path $PSScriptRoot -Parent +$script:resourceSetHelperFilePath = Join-Path -Path $script:dscResourcesFolderFilePath -ChildPath 'ResourceSetHelper.psm1' +Import-Module -Name $script:resourceSetHelperFilePath +<# + .SYNOPSIS + A composite DSC resource to configure a set of similar xWindowsFeature resources. + + .PARAMETER Name + The name of the roles or features to install or uninstall. + + .PARAMETER Ensure + Specifies whether the roles or features should be installed or uninstalled. + + To install the features, set this property to Present. + To uninstall the features, set this property to Absent. + + .PARAMETER IncludeAllSubFeature + Specifies whether or not all subfeatures should be installed or uninstalled alongside the specified roles or features. + + If this property is true and Ensure is set to Present, all subfeatures will be installed. + If this property is false and Ensure is set to Present, subfeatures will not be installed or uninstalled. + If Ensure is set to Absent, all subfeatures will be uninstalled. + + .PARAMETER Credential + The credential of the user account under which to install or uninstall the role or feature. + + .PARAMETER LogPath + The custom file path to which to log this operation. + If not passed in, the default log path will be used (%windir%\logs\ServerManager.log). +#> Configuration xWindowsFeatureSet { [CmdletBinding()] @@ -26,6 +56,7 @@ Configuration xWindowsFeatureSet [ValidateNotNull()] [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Credential, [ValidateNotNullOrEmpty()] diff --git a/Examples/Sample_xWindowsFeatureSet.ps1 b/Examples/Sample_xWindowsFeatureSet.ps1 deleted file mode 100644 index 51c7d51ec..000000000 --- a/Examples/Sample_xWindowsFeatureSet.ps1 +++ /dev/null @@ -1,31 +0,0 @@ -<# - .SYNOPSIS - Installs the set of features named with all their subfeatures from the specified source. -#> -Configuration $configurationName -{ - param ( - [Parameter(Mandatory = $true)] - [String[]] - $FeatureNames, - - [Parameter(Mandatory = $true)] - [String] - $LogPath, - - [Parameter(Mandatory = $true)] - [String] - $Source - ) - - Import-DscResource -ModuleName xPSDesiredStateConfiguration - - xWindowsFeatureSet WindowsFeatureSet1 - { - Name = $FeatureNames - Ensure = "Present" - IncludeAllSubFeature = $true - LogPath = $LogPath - Source = $Source - } -} diff --git a/Examples/Sample_xWindowsFeatureSet_Install.ps1 b/Examples/Sample_xWindowsFeatureSet_Install.ps1 new file mode 100644 index 000000000..7703b6224 --- /dev/null +++ b/Examples/Sample_xWindowsFeatureSet_Install.ps1 @@ -0,0 +1,20 @@ +<# + .SYNOPSIS + Installs the TelnetClient and RSAT-File-Services Windows features, including all their + subfeatures. Logs the operation to the file at 'C:\LogPath\Log.log'. +#> +Configuration xWindowsFeatureSetExample_Install +{ + [CmdletBinding()] + param () + + Import-DscResource -ModuleName 'xPSDesiredStateConfiguration' + + xWindowsFeatureSet WindowsFeatureSet1 + { + Name = @( 'Telnet-Client', 'RSAT-File-Services' ) + Ensure = 'Present' + IncludeAllSubFeature = $true + LogPath = 'C:\LogPath\Log.log' + } +} diff --git a/Examples/Sample_xWindowsFeatureSet_Uninstall.ps1 b/Examples/Sample_xWindowsFeatureSet_Uninstall.ps1 new file mode 100644 index 000000000..9b3ea3eb5 --- /dev/null +++ b/Examples/Sample_xWindowsFeatureSet_Uninstall.ps1 @@ -0,0 +1,20 @@ +<# + .SYNOPSIS + Uninstalls the TelnetClient and RSAT-File-Services Windows features, including all their + subfeatures. Logs the operation to the file at 'C:\LogPath\Log.log'. +#> +Configuration xWindowsFeatureSetExample_Install +{ + [CmdletBinding()] + param () + + Import-DscResource -ModuleName 'xPSDesiredStateConfiguration' + + xWindowsFeatureSet WindowsFeatureSet1 + { + Name = @( 'Telnet-Client', 'RSAT-File-Services' ) + Ensure = 'Absent' + IncludeAllSubFeature = $true + LogPath = 'C:\LogPath\Log.log' + } +} diff --git a/README.md b/README.md index daae08446..6d5f92bf3 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Please check out common DSC Resources [contributing guidelines](https://github.c * **xProcessSet** allows starting and stopping of a group of windows processes with no arguments. * **xUser** provides a mechanism to manage local users on the target node. * **xWindowsFeature** provides a mechanism to install or uninstall Windows roles or features on a target node. -* **xWindowsFeatureSet** allows installation and uninstallation of a group of Windows features and their subfeatures. +* **xWindowsFeatureSet** provides a mechanism to configure and manage multiple xWindowsFeature resources on a target node. * **xWindowsOptionalFeature** provides a mechanism to enable or disable optional features on a target node. * **xWindowsOptionalFeatureSet** provides a mechanism to configure and manage multiple xWindowsOptionalFeature resources on a target node. * **xWindowsPackageCab** provides a mechanism to install or uninstall a package from a Windows cabinet (cab) file on a target node. @@ -372,6 +372,7 @@ These parameters will be the same for each process in the set. Please refer to t * **WorkingDirectory**: The directory to run the processes under. ### xWindowsFeature + Provides a mechanism to install or uninstall Windows roles or features on a target node. #### Requirements @@ -397,20 +398,34 @@ Provides a mechanism to install or uninstall Windows roles or features on a targ * [Install or uninstall a Windows feature](https://github.com/PowerShell/xPSDesiredStateConfiguration/blob/dev/Examples/Sample_xWindowsFeature.ps1) ### xWindowsFeatureSet -* **Name**: Defines the names of the Windows features in the set. -These parameters will be the same for each Windows feature in the set. Please refer to the xWindowsFeature section above for more details on these parameters: -* **Ensure**: Ensures that the set of features is present or absent. - - Supported values: Present, Absent. - - Default Value: Present. -* **Credential**: Indicates the credentials to use to add or remove the role or feature. -* **IncludeAllSubFeature**: Set this property to $true to ensure the state of all required subfeatures matches the state of the Ensure property. - - Suported values: $true, $false. - - Default value: $false. -* **LogPath**: Indicates the path to a log file where you want the resource provider to log the operation. -* **Source**: Indicates the location of the source file to use for installation, if necessary. +Provides a mechanism to configure and manage multiple xWindowsFeature resources on a target node. + +#### Requirements + +* Target machine must be running Windows Server 2008 or later. +* Target machine must have access to the DISM PowerShell module. +* Target machine must have access to the ServerManager module. + +#### Parameters + +* **[String] Name** _(Key)_: The names of the roles or features that you want install or uninstall. This may be different from the display name of the feature/role. To retrieve the names of features/roles on a machine use the Get-WindowsFeature cmdlet. +* **[String] Ensure** _(Write)_: Specifies whether the feature should be installed or uninstalled. To install features, set this property to Present. To uninstall features, set this property to Absent. { Present | Absent }. +* **[Boolean] IncludeAllSubFeature** _(Write)_: Specifies whether or not all subfeatures should be installed or uninstalled alongside the specified roles or features. If this property is true and Ensure is set to Present, all subfeatures will be installed. If this property is false and Ensure is set to Present, subfeatures will not be installed or uninstalled. If Ensure is set to Absent, all subfeatures will be uninstalled. +* **[PSCredential] Credential** _(Write)_: The credential of the user account under which to install or uninstall the role or feature. +* **[String] LogPath** _(Write)_: The custom file path to which to log this operation. If not passed in, the default log path will be used (%windir%\logs\ServerManager.log). + +#### Read-Only Properties from Get-TargetResource + +* **[String] DisplayName** _(Read)_: The display names of the retrieved roles or features. + +#### Examples + +* [Install multiple Windows features](https://github.com/PowerShell/xPSDesiredStateConfiguration/blob/dev/Examples/Sample_xWindowsFeatureSet_Install.ps1) +* [Uninstall multiple Windows features](https://github.com/PowerShell/xPSDesiredStateConfiguration/blob/dev/Examples/Sample_xWindowsFeatureSet_Uninstall.ps1) ### xWindowsOptionalFeature + Provides a mechanism to enable or disable optional features on a target node. This resource works on Nano Server. @@ -439,6 +454,7 @@ This resource works on Nano Server. * [Enable the specified windows optional feature and output logs to the specified path](https://github.com/PowerShell/xPSDesiredStateConfiguration/blob/dev/Examples/Sample_xWindowsOptionalFeature.ps1) ### xWindowsOptionalFeatureSet + Provides a mechanism to configure and manage multiple xWindowsOptionalFeature resources on a target node. This resource works on Nano Server. @@ -548,6 +564,9 @@ None * xWindowsOptionalFeatureSet * Updated resource to use new ResouceSetHelper functions and added integration tests. * Updated documentation and example +* xWindowsFeatureSet + * Updated resource to use new ResouceSetHelper functions and added integration tests. + * Updated documentation and example ### 5.0.0.0 diff --git a/Tests/Integration/MSFT_xWindowsFeature.Integration.Tests.ps1 b/Tests/Integration/MSFT_xWindowsFeature.Integration.Tests.ps1 index 5f29af422..589708b36 100644 --- a/Tests/Integration/MSFT_xWindowsFeature.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_xWindowsFeature.Integration.Tests.ps1 @@ -1,10 +1,8 @@ <# Integration tests for Installing/uninstalling a Windows Feature. Currently Telnet-Client is - set as the feature to test since it's fairly small and doesn't require a restart. ADRMS - is set as the feature to test installing/uninstalling a feature with subfeatures, - but this takes a good chunk of time, so by default these tests are set to be skipped. - If there's any major changes to the resource, then set the skipLongTests variable to $false - and run those tests at least once to test the new functionality more completely. + set as the feature to test since it's fairly small and doesn't require a restart. + RSAT-File-Services is set as the feature to test installing/uninstalling a feature with + subfeatures. #> # Suppressing this rule since we need to create a plaintext password to test this resource @@ -19,6 +17,7 @@ $script:testEnvironment = Enter-DscResourceTestEnvironment ` -DscResourceModuleName 'xPSDesiredStateConfiguration' ` -DscResourceName 'MSFT_xWindowsFeature' ` -TestType 'Integration' + <# If this is set to $true then the tests that test installing/uninstalling a feature with its subfeatures will not run. diff --git a/Tests/Integration/xWindowsFeatureSet.Integration.Tests.ps1 b/Tests/Integration/xWindowsFeatureSet.Integration.Tests.ps1 index f68ba5108..21f7fcee3 100644 --- a/Tests/Integration/xWindowsFeatureSet.Integration.Tests.ps1 +++ b/Tests/Integration/xWindowsFeatureSet.Integration.Tests.ps1 @@ -1,52 +1,207 @@ -Set-StrictMode -Version 'latest' -$errorActionPreference = 'stop' +$errorActionPreference = 'Stop' +Set-StrictMode -Version 'Latest' -Import-Module -Name (Join-Path -Path (Split-Path $PSScriptRoot -Parent) -ChildPath 'CommonTestHelper.psm1') +# Import CommonTestHelper for Enter-DscResourceTestEnvironment, Exit-DscResourceTestEnvironment +$script:testsFolderFilePath = Split-Path $PSScriptRoot -Parent +$script:commonTestHelperFilePath = Join-Path -Path $testsFolderFilePath -ChildPath 'CommonTestHelper.psm1' +Import-Module -Name $commonTestHelperFilePath $script:testEnvironment = Enter-DscResourceTestEnvironment ` -DscResourceModuleName 'xPSDesiredStateConfiguration' ` -DscResourceName 'xWindowsFeatureSet' ` -TestType 'Integration' -Describe "xWindowsFeatureSet Integration Tests" { - It "Prepare to install a set of Windows features with their sub features" { - $configurationName = "PrepareInstallWithSubFeatures" - $configurationPath = Join-Path -Path (Get-Location) -ChildPath $configurationName - $logPath = Join-Path -Path (Get-Location) -ChildPath "TestLogs" - $featureNames = @("Test1", "SubTest3") +try { + Describe 'xWindowsFeatureSet Integration Tests' { + BeforeAll { + $script:validFeatureNames = @( 'Telnet-Client', 'RSAT-File-Services' ) - try - { - Configuration $configurationName + $script:originalFeatures = @{} + + foreach ($validFeatureName in $script:validFeatureNames) + { + $script:originalFeatures[$validFeatureName] = Get-WindowsFeature -Name $validFeatureName + } + + $script:configurationFilePath = Join-Path -Path $PSScriptRoot -ChildPath 'xWindowsFeatureSet.config.ps1' + } + + AfterAll { + foreach ($validFeatureaName in $script:originalFeatures.Keys) { - Import-DscResource -ModuleName xPSDesiredStateConfiguration + $feature = Get-WindowsFeature -Name $validFeatureaName - xWindowsFeatureSet xWindowsFeatureSet1 + if ($script:originalFeatures[$validFeatureaName].Installed -and -not $feature.Installed) + { + Add-WindowsFeature -Name $validFeatureaName + } + elseif (-not $script:originalFeatures[$validFeatureaName].Installed -and $feature.Installed) { - Name = $featureNames - Ensure = "Present" - IncludeAllSubFeature = $true - LogPath = $logPath + Remove-WindowsFeature -Name $validFeatureaName } } + } + + Context 'Install two Windows Features' { + $configurationName = 'InstallTwoFeatures' + + $windowsFeatureSetParameters = @{ + WindowsFeatureNames = $script:validFeatureNames + Ensure = 'Present' + LogPath = Join-Path -Path $TestDrive -ChildPath 'InstallFeatureSetTest.log' + } + + foreach ($windowsFeatureName in $windowsFeatureSetParameters.WindowsFeatureNames) + { + $windowsFeature = Get-WindowsFeature -Name $windowsFeatureName + + It "Should be able to retrieve Windows feature $windowsFeatureName before the configuration" { + $windowsFeature | Should Not Be $null + } - # Ensure that the configuration compiles correctly - { & $configurationName -OutputPath $configurationPath } | Should Not Throw + if ($windowsFeature.Installed) + { + $null = Remove-WindowsFeature -Name $windowsFeatureName + $windowsFeature = Get-WindowsFeature -FeatureName $windowsFeatureName + + # May need to wait a moment for the correct state to populate + $millisecondsElapsed = 0 + $startTime = Get-Date + while ($windowsFeature.Installed -and $millisecondsElapsed -lt 3000) + { + $windowsFeature = Get-WindowsFeature -Name $windowsFeatureName + $millisecondsElapsed = ((Get-Date) - $startTime).TotalMilliseconds + } + } + + It "Should have uninstalled Windows feature $windowsFeatureName before the configuration" { + $windowsFeature.Installed | Should Be $false + } + } + + It 'Should compile and run configuration' { + { + . $script:configurationFilePath -ConfigurationName $configurationName + & $configurationName -OutputPath $TestDrive @windowsFeatureSetParameters + Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force + } | Should Not Throw + } + + foreach ($windowsFeatureName in $windowsFeatureSetParameters.WindowsFeatureNames) + { + $windowsFeature = Get-WindowsFeature -Name $windowsFeatureName + + It "Should be able to retrieve Windows feature $windowsFeatureName after the configuration" { + $windowsFeature | Should Not Be $null + } + + if (-not $windowsFeature.Installed) + { + # May need to wait a moment for the correct state to populate + $millisecondsElapsed = 0 + $startTime = Get-Date + while (-not $windowsFeature.Installed -and $millisecondsElapsed -lt 3000) + { + $windowsFeature = Get-WindowsFeature -Name $windowsFeatureName + $millisecondsElapsed = ((Get-Date) - $startTime).TotalMilliseconds + } + } - # This call will not actually work since we are not on a server and these are not real features - { Start-DscConfiguration -Path $configurationPath -ErrorAction SilentlyContinue } | Should Not Throw + It "Should have installed Windows feature $windowsFeatureName after the configuration" { + $windowsFeature.Installed | Should Be $true + } + } + + It 'Should have created the log file' { + Test-Path -Path $windowsFeatureSetParameters.LogPath | Should Be $true + } + + It 'Should have created content in the log file' { + Get-Content -Path $windowsFeatureSetParameters.LogPath -Raw | Should Not Be $null + } } - finally - { - if (Test-Path -Path $logPath) + + Context 'Uninstall two Windows features' { + $configurationName = 'UninstallTwoFeatures' + + $windowsFeatureSetParameters = @{ + WindowsFeatureNames = $script:validFeatureNames + Ensure = 'Absent' + LogPath = Join-Path -Path $TestDrive -ChildPath 'UninstallFeatureSetTest.log' + } + + foreach ($windowsFeatureName in $windowsFeatureSetParameters.WindowsFeatureNames) { - Remove-Item -Path $logPath -Recurse -Force + $windowsFeature = Get-WindowsFeature -Name $windowsFeatureName + + It "Should be able to retrieve Windows feature $windowsFeatureName before the configuration" { + $windowsFeature | Should Not Be $null + } + + if (-not $windowsFeature.Installed) + { + $null = Add-WindowsFeature -Name $windowsFeatureName + $windowsFeature = Get-WindowsFeature -FeatureName $windowsFeatureName + + # May need to wait a moment for the correct state to populate + $millisecondsElapsed = 0 + $startTime = Get-Date + while (-not $windowsFeature.Installed -and $millisecondsElapsed -lt 3000) + { + $windowsFeature = Get-WindowsFeature -Name $windowsFeatureName + $millisecondsElapsed = ((Get-Date) - $startTime).TotalMilliseconds + } + } + + It "Should have installed Windows feature $windowsFeatureName before the configuration" { + $windowsFeature.Installed | Should Be $true + } + } + + It 'Should compile and run configuration' { + { + . $script:configurationFilePath -ConfigurationName $configurationName + & $configurationName -OutputPath $TestDrive @windowsFeatureSetParameters + Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force + } | Should Not Throw } - if (Test-Path -Path $configurationPath) + foreach ($windowsFeatureName in $windowsFeatureSetParameters.WindowsFeatureNames) { - Remove-Item -Path $configurationPath -Recurse -Force + $windowsFeature = Get-WindowsFeature -Name $windowsFeatureName + + It "Should be able to retrieve Windows feature $windowsFeatureName after the configuration" { + $windowsFeature | Should Not Be $null + } + + if ($windowsFeature.Installed) + { + # May need to wait a moment for the correct state to populate + $millisecondsElapsed = 0 + $startTime = Get-Date + while ($windowsFeature.Installed -and $millisecondsElapsed -lt 3000) + { + $windowsFeature = Get-WindowsFeature -Name $windowsFeatureName + $millisecondsElapsed = ((Get-Date) - $startTime).TotalMilliseconds + } + } + + It "Should have uninstalled Windows feature $windowsFeatureName after the configuration" { + $windowsFeature.Installed | Should Be $false + } + } + + It 'Should have created the log file' { + Test-Path -Path $windowsFeatureSetParameters.LogPath | Should Be $true + } + + It 'Should have created content in the log file' { + Get-Content -Path $windowsFeatureSetParameters.LogPath -Raw | Should Not Be $null } } } } +finally +{ + Exit-DscResourceTestEnvironment -TestEnvironment $script:testEnvironment +} diff --git a/Tests/Integration/xWindowsFeatureSet.config.ps1 b/Tests/Integration/xWindowsFeatureSet.config.ps1 new file mode 100644 index 000000000..635697443 --- /dev/null +++ b/Tests/Integration/xWindowsFeatureSet.config.ps1 @@ -0,0 +1,35 @@ +param +( + [Parameter(Mandatory = $true)] + [String] + $ConfigurationName +) + +Configuration $ConfigurationName +{ + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String[]] + $WindowsFeatureNames, + + [ValidateSet('Present', 'Absent')] + [String] + $Ensure = 'Present', + + [ValidateNotNullOrEmpty()] + [String] + $LogPath + ) + + Import-DscResource -ModuleName 'xPSDesiredStateConfiguration' + + xWindowsFeatureSet xWindowsFeatureSet1 + { + Name = $WindowsFeatureNames + Ensure = $Ensure + LogPath = $LogPath + IncludeAllSubfeature = $false + } +} From e6f9631d98887cdc2b2fdb780d0ce7ccdd7e9077 Mon Sep 17 00:00:00 2001 From: Katie Keim Date: Thu, 15 Dec 2016 15:10:50 -0800 Subject: [PATCH 3/4] Clean ProcessSet and add tests --- DSCResources/xProcessSet/xProcessSet.psd1 | 88 +-------- .../xProcessSet/xProcessSet.schema.psm1 | 59 +++++- Examples/Sample_xProcessSet.ps1 | 10 - Examples/Sample_xProcessSet_Start.ps1 | 18 ++ Examples/Sample_xProcessSet_Stop.ps1 | 20 ++ README.md | 64 ++++-- .../xProcessSet.Integration.Tests.ps1 | 187 ++++++++++-------- Tests/Integration/xProcessSet.config.ps1 | 29 +++ Tests/WindowsProcessTestProcess.cs | 14 +- Tests/WindowsProcessTestProcessSet.exe | Bin 0 -> 5632 bytes 10 files changed, 281 insertions(+), 208 deletions(-) delete mode 100644 Examples/Sample_xProcessSet.ps1 create mode 100644 Examples/Sample_xProcessSet_Start.ps1 create mode 100644 Examples/Sample_xProcessSet_Stop.ps1 create mode 100644 Tests/Integration/xProcessSet.config.ps1 create mode 100644 Tests/WindowsProcessTestProcessSet.exe diff --git a/DSCResources/xProcessSet/xProcessSet.psd1 b/DSCResources/xProcessSet/xProcessSet.psd1 index b93c9302f..222858a1d 100644 --- a/DSCResources/xProcessSet/xProcessSet.psd1 +++ b/DSCResources/xProcessSet/xProcessSet.psd1 @@ -19,93 +19,9 @@ CompanyName = 'Microsoft Corporation' Copyright = '(c) 2016 Microsoft. All rights reserved.' # Description of the functionality provided by this module -Description = 'Allows starting and stopping of a group of windows processes with no arguments.' +Description = 'Provides a mechanism to configure and manage multiple xWindowsProcess resources on a target node.' # Minimum version of the Windows PowerShell engine required by this module -# PowerShellVersion = '' - -# Name of the Windows PowerShell host required by this module -# PowerShellHostName = '' - -# Minimum version of the Windows PowerShell host required by this module -# PowerShellHostVersion = '' - -# Minimum version of Microsoft .NET Framework required by this module -# DotNetFrameworkVersion = '' - -# Minimum version of the common language runtime (CLR) required by this module -# CLRVersion = '' - -# Processor architecture (None, X86, Amd64) required by this module -# ProcessorArchitecture = '' - -# Modules that must be imported into the global environment prior to importing this module -# RequiredModules = @() - -# Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() - -# Script files (.ps1) that are run in the caller's environment prior to importing this module. -# ScriptsToProcess = @() - -# Type files (.ps1xml) to be loaded when importing this module -# TypesToProcess = @() - -# Format files (.ps1xml) to be loaded when importing this module -# FormatsToProcess = @() - -# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess -# NestedModules = @() - -# Functions to export from this module -FunctionsToExport = '*' - -# Cmdlets to export from this module -# CmdletsToExport = '*' - -# Variables to export from this module -# VariablesToExport = '*' - -# Aliases to export from this module -# AliasesToExport = '*' - -# DSC resources to export from this module -# DscResourcesToExport = '*' - -# List of all modules packaged with this module -# ModuleList = @() - -# List of all files packaged with this module -# FileList = @() - -# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. -PrivateData = @{ - - PSData = @{ - - # Tags applied to this module. These help with module discovery in online galleries. - # Tags = @() - - # A URL to the license for this module. - # LicenseUri = '' - - # A URL to the main website for this project. - # ProjectUri = '' - - # A URL to an icon representing this module. - # IconUri = '' - - # ReleaseNotes of this module - # ReleaseNotes = '' - - } # End of PSData hashtable - -} # End of PrivateData hashtable - -# HelpInfo URI of this module -# HelpInfoURI = '' - -# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. -# DefaultCommandPrefix = '' +PowerShellVersion = '4.0' } diff --git a/DSCResources/xProcessSet/xProcessSet.schema.psm1 b/DSCResources/xProcessSet/xProcessSet.schema.psm1 index cde3e7c48..803ceef9f 100644 --- a/DSCResources/xProcessSet/xProcessSet.schema.psm1 +++ b/DSCResources/xProcessSet/xProcessSet.schema.psm1 @@ -1,8 +1,48 @@ -Set-StrictMode -Version 'latest' -$errorActionPreference = 'stop' +$errorActionPreference = 'Stop' +Set-StrictMode -Version 'Latest' -Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'ResourceSetHelper.psm1') +# Import ResourceSetHelper for New-ResourceSetConfigurationScriptBlock +$script:dscResourcesFolderFilePath = Split-Path -Path $PSScriptRoot -Parent +$script:resourceSetHelperFilePath = Join-Path -Path $script:dscResourcesFolderFilePath -ChildPath 'ResourceSetHelper.psm1' +Import-Module -Name $script:resourceSetHelperFilePath +<# + .SYNOPSIS + A composite DSC resource to configure a set of similar xWindowsProcess resources. + No arguments can be passed into these xWindowsProcess resources. + + .PARAMETER Path + The file paths to the executables of the processes to start or stop. Only the names of the + files may be specified if they are all accessible through the environment path. Relative + paths are not supported. + + .PARAMETER Ensure + Specifies whether or not the processes should exist. + + To start processes, set this property to Present. + To stop processes, set this property to Absent. + + .PARAMETER Credential + The credential of the user account to start the processes under. + + .PARAMETER StandardOutputPath + The file path to write the standard output to. Any existing file at this path + will be overwritten.This property cannot be specified at the same time as Credential + when running the processes as a local user. + + .PARAMETER StandardErrorPath + The file path to write the standard error output to. Any existing file at this path + will be overwritten. + + .PARAMETER StandardInputPath + The file path to get standard input from. This property cannot be specified at the + same time as Credential when running the processes as a local user. + + .PARAMETER WorkingDirectory + The file path to use as the working directory for the processes. Any existing file + at this path will be overwritten. This property cannot be specified at the same time + as Credential when running the processes as a local user. +#> Configuration xProcessSet { [CmdletBinding()] @@ -13,23 +53,28 @@ Configuration xProcessSet [String[]] $Path, - [ValidateNotNullOrEmpty()] - [System.Management.Automation.PSCredential] - $Credential, - [ValidateSet('Present', 'Absent')] [String] $Ensure, + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $Credential, + + [ValidateNotNullOrEmpty()] [String] $StandardOutputPath, + [ValidateNotNullOrEmpty()] [String] $StandardErrorPath, + [ValidateNotNullOrEmpty()] [String] $StandardInputPath, + [ValidateNotNullOrEmpty()] [String] $WorkingDirectory ) diff --git a/Examples/Sample_xProcessSet.ps1 b/Examples/Sample_xProcessSet.ps1 deleted file mode 100644 index bb410e5fd..000000000 --- a/Examples/Sample_xProcessSet.ps1 +++ /dev/null @@ -1,10 +0,0 @@ -Configuration xProcessSetExample -{ - Import-DscResource -ModuleName xPSDesiredStateConfiguration - - xProcessSet ProcessSet1 - { - Path = @("C:\Windows\System32\cmd.exe", "C:\Windows\System32\Notepad.exe") - Ensure = "Present" - } -} diff --git a/Examples/Sample_xProcessSet_Start.ps1 b/Examples/Sample_xProcessSet_Start.ps1 new file mode 100644 index 000000000..c8001557c --- /dev/null +++ b/Examples/Sample_xProcessSet_Start.ps1 @@ -0,0 +1,18 @@ +<# + .SYNOPSIS + Starts the processes with the executables at the file paths C:\Windows\cmd.exe and + C:\TestPath\TestProcess.exe with no arguments. +#> +Configuration Sample_xProcessSet_Start +{ + [CmdletBinding()] + param () + + Import-DscResource -ModuleName 'xPSDesiredStateConfiguration' + + xProcessSet xProcessSet1 + { + Path = @( 'C:\Windows\System32\cmd.exe', 'C:\TestPath\TestProcess.exe' ) + Ensure = 'Present' + } +} diff --git a/Examples/Sample_xProcessSet_Stop.ps1 b/Examples/Sample_xProcessSet_Stop.ps1 new file mode 100644 index 000000000..0e67f26a8 --- /dev/null +++ b/Examples/Sample_xProcessSet_Stop.ps1 @@ -0,0 +1,20 @@ +<# + .SYNOPSIS + Stops the processes with the executables at the file paths C:\Windows\cmd.exe and + C:\TestPath\TestProcess.exe with no arguments and logs any output to the path + C:\OutputPath\Output.log. +#> +Configuration Sample_xProcessSet_Stop +{ + [CmdletBinding()] + param () + + Import-DscResource -ModuleName 'xPSDesiredStateConfiguration' + + xProcessSet xProcessSet1 + { + Path = @( 'C:\Windows\System32\cmd.exe', 'C:\TestPath\TestProcess.exe' ) + Ensure = 'Absent' + StandardOutputPath = 'C:\OutputPath\Output.log' + } +} diff --git a/README.md b/README.md index 6d5f92bf3..06311ce92 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Please check out common DSC Resources [contributing guidelines](https://github.c * **xRegistry** provides a mechanism to manage registry keys and values on a target node. * **xEnvironment** configures and manages environment variables. * **xWindowsProcess** provides a mechanism to start and stop a Windows process. -* **xProcessSet** allows starting and stopping of a group of windows processes with no arguments. +* **xProcessSet** provides a mechanism to configure and manage multiple xWindowsProcess resources on a target node. * **xUser** provides a mechanism to manage local users on the target node. * **xWindowsFeature** provides a mechanism to install or uninstall Windows roles or features on a target node. * **xWindowsFeatureSet** provides a mechanism to configure and manage multiple xWindowsFeature resources on a target node. @@ -135,6 +135,7 @@ None * [Add members to multiple groups](https://github.com/PowerShell/xPSDesiredStateConfiguration/blob/dev/Examples/Sample_xGroupSet_AddMembers.ps1) ### xWindowsProcess + Provides a mechanism to start and stop a Windows process. #### Requirements @@ -142,6 +143,7 @@ Provides a mechanism to start and stop a Windows process. None #### Parameters + * **[String] Path** _(Key)_: The executable file of the process. This can be defined as either the full path to the file or as the name of the file if it is accessible through the environment path. Relative paths are not supported. * **[String] Arguments** _(Key)_: A single string containing all the arguments to pass to the process. Pass in an empty string if no arguments are needed. * **[PSCredential] Credential** _(Write)_: The credential of the user account to run the process under. If this user is from the local system, the StandardOutputPath, StandardInputPath, and WorkingDirectory parameters cannot be provided at the same time. @@ -149,9 +151,10 @@ None * **[String] StandardOutputPath** _(Write)_: The file path to which to write the standard output from the process. Any existing file at this file path will be overwritten. This property cannot be specified at the same time as Credential when running the process as a local user. * **[String] StandardErrorPath** _(Write)_: The file path to which to write the standard error output from the process. Any existing file at this file path will be overwritten. * **[String] StandardInputPath** _(Write)_: The file path from which to receive standard input for the process. This property cannot be specified at the same time as Credential when running the process as a local user. -* **[String] WorkingDirectory** _(Write)_: The file path to the working directory under which to run the file. This property cannot be specified at the same time as Credential when running the process as a local user. +* **[String] WorkingDirectory** _(Write)_: The file path to the working directory under which to run the process. This property cannot be specified at the same time as Credential when running the process as a local user. #### Read-Only Properties from Get-TargetResource + * **[UInt64] PagedMemorySize** _(Read)_: The amount of paged memory, in bytes, allocated for the process. * **[UInt64] NonPagedMemorySize** _(Read)_: The amount of nonpaged memory, in bytes, allocated for the process. * **[UInt64] VirtualMemorySize** _(Read)_: The amount of virtual memory, in bytes, allocated for the process. @@ -166,6 +169,41 @@ None * [Start a process under a user](https://github.com/PowerShell/xPSDesiredStateConfiguration/blob/dev/Examples/Sample_xWindowsProcess_StartUnderUser.ps1) * [Stop a process under a user](https://github.com/PowerShell/xPSDesiredStateConfiguration/blob/dev/Examples/Sample_xWindowsProcess_StopUnderUser.ps1) +### xProcessSet + +Provides a mechanism to configure and manage multiple xWindowsProcess resources on a target node. + +#### Requirements + +None + +#### Parameters + +* **[String[]] Path** _(Key)_: The file paths to the executables of the processes to start or stop. Only the names of the files may be specified if they are all accessible through the environment path. Relative paths are not supported. + +The following parameters will be the same for each process in the set: + +* **[PSCredential] Credential** _(Write)_: The credential of the user account to run the processes under. If this user is from the local system, the StandardOutputPath, StandardInputPath, and WorkingDirectory parameters cannot be provided at the same time. +* **[String] Ensure** _(Write)_: Specifies whether or not the processes should be running. To start the processes, specify this property as Present. To stop the processes, specify this property as Absent. { Present | Absent }. +* **[String] StandardOutputPath** _(Write)_: The file path to which to write the standard output from the processes. Any existing file at this file path will be overwritten. This property cannot be specified at the same time as Credential when running the processes as a local user. +* **[String] StandardErrorPath** _(Write)_: The file path to which to write the standard error output from the processes. Any existing file at this file path will be overwritten. +* **[String] StandardInputPath** _(Write)_: The file path from which to receive standard input for the processes. This property cannot be specified at the same time as Credential when running the processes as a local user. +* **[String] WorkingDirectory** _(Write)_: The file path to the working directory under which to run the process. This property cannot be specified at the same time as Credential when running the processes as a local user. + +#### Read-Only Properties from Get-TargetResource + +* **[UInt64] PagedMemorySize** _(Read)_: The amount of paged memory, in bytes, allocated for the processes. +* **[UInt64] NonPagedMemorySize** _(Read)_: The amount of nonpaged memory, in bytes, allocated for the processes. +* **[UInt64] VirtualMemorySize** _(Read)_: The amount of virtual memory, in bytes, allocated for the processes. +* **[SInt32] HandleCount** _(Read)_: The number of handles opened by the processes. +* **[SInt32] ProcessId** _(Read)_: The unique identifier of the processes. +* **[SInt32] ProcessCount** _(Read)_: The number of instances of the given processes that are currently running. + +#### Examples + +* [Start multiple processes](https://github.com/PowerShell/xPSDesiredStateConfiguration/blob/dev/Examples/Sample_xProcessSet_Start.ps1) +* [Stop multiple processes](https://github.com/PowerShell/xPSDesiredStateConfiguration/blob/dev/Examples/Sample_xProcessSet_Stop.ps1) + ### xService Provides a mechanism to configure and manage Windows services. This resource works on Nano Server. @@ -356,21 +394,6 @@ None * [Create a new User](https://github.com/PowerShell/xPSDesiredStateConfiguration/blob/dev/Examples/Sample_xUser_CreateUser.ps1) -### xProcessSet -Note: All processes in a process set will run without arguments. - -* **Path**: Defines the path to each process in the set. - -These parameters will be the same for each process in the set. Please refer to the xWindowsProcess section above for more details on these parameters: -* **Credential**: The credentials of the user under whose context you want to run the process. -* **Ensure**: Ensures that the process is running or stopped. - - Supported values: Present, Absent - - Default Value: Present -* **StandardOutputPath**: The path to write the standard output stream to. -* **StandardErrorPath**: The path to write the standard error stream to. -* **StandardInputPath**: The path to receive standard input from. -* **WorkingDirectory**: The directory to run the processes under. - ### xWindowsFeature Provides a mechanism to install or uninstall Windows roles or features on a target node. @@ -563,10 +586,13 @@ None * Changed name to be xWindowsProcess everywhere. * xWindowsOptionalFeatureSet * Updated resource to use new ResouceSetHelper functions and added integration tests. - * Updated documentation and example + * Updated documentation and examples * xWindowsFeatureSet * Updated resource to use new ResouceSetHelper functions and added integration tests. - * Updated documentation and example + * Updated documentation and examples +* xProcessSet + * Updated resource to use new ResouceSetHelper functions and added integration tests. + * Updated documentation and examples ### 5.0.0.0 diff --git a/Tests/Integration/xProcessSet.Integration.Tests.ps1 b/Tests/Integration/xProcessSet.Integration.Tests.ps1 index 87e7b57e0..50c001984 100644 --- a/Tests/Integration/xProcessSet.Integration.Tests.ps1 +++ b/Tests/Integration/xProcessSet.Integration.Tests.ps1 @@ -1,116 +1,147 @@ -Set-StrictMode -Version 'latest' -$errorActionPreference = 'stop' +$errorActionPreference = 'Stop' +Set-StrictMode -Version 'Latest' -Import-Module -Name (Join-Path -Path (Split-Path $PSScriptRoot -Parent) -ChildPath 'CommonTestHelper.psm1') +# Import CommonTestHelper for Enter-DscResourceTestEnvironment, Exit-DscResourceTestEnvironment +$script:testsFolderFilePath = Split-Path $PSScriptRoot -Parent +$script:commonTestHelperFilePath = Join-Path -Path $testsFolderFilePath -ChildPath 'CommonTestHelper.psm1' +Import-Module -Name $commonTestHelperFilePath $script:testEnvironment = Enter-DscResourceTestEnvironment ` -DscResourceModuleName 'xPSDesiredStateConfiguration' ` -DscResourceName 'xProcessSet' ` -TestType 'Integration' -Describe "xProcessSet Integration Tests" { - BeforeAll { - $script:testsFolderFilePath = Split-Path -Path $PSScriptRoot -Parent - $script:processTestHelperFilePath = Join-Path -Path $script:testsFolderFilePath ` - -ChildPath 'MSFT_xWindowsProcess.TestHelper.psm1' +try +{ + Describe 'xProcessSet Integration Tests' { + BeforeAll { + $script:configurationFilePath = Join-Path -Path $PSScriptRoot -ChildPath 'xProcessSet.config.ps1' - Import-Module -Name $script:processTestHelperFilePath + $originalProcessPath = Join-Path -Path $script:testsFolderFilePath -ChildPath 'WindowsProcessTestProcessSet.exe' + $copiedProcessPath = Join-Path -Path $TestDrive -ChildPath 'TestWindowsProcess2.exe' - $script:cmdProcess1ShortName = 'ProcessTest1' - $script:cmdProcess1FullName = 'ProcessTest1.exe' - $script:cmdProcess1FullPath = "$env:WinDir\system32\ProcessTest1.exe" - Copy-Item "$env:WinDir\system32\cmd.exe" $script:cmdProcess1FullPath -Force -ErrorAction SilentlyContinue + Copy-Item -Path $originalProcessPath -Destination $copiedProcessPath -Force - $script:cmdProcess2ShortName = 'ProcessTest2' - $script:cmdProcess2FullName = 'ProcessTest2.exe' - $script:cmdProcess2FullPath = "$env:WinDir\system32\ProcessTest2.exe" - Copy-Item "$env:WinDir\system32\cmd.exe" $script:cmdProcess2FullPath -Force -ErrorAction SilentlyContinue - } + $script:processPaths = @( $originalProcessPath, $copiedProcessPath) + } - AfterEach { - Stop-ProcessByName -ProcessName $script:cmdProcess1ShortName - Stop-ProcessByName -ProcessName $script:cmdProcess2ShortName - } + AfterAll { + foreach ($processPath in $script:processPaths) + { + $processName = [System.IO.Path]::GetFileNameWithoutExtension($processPath) + $process = Get-Process -Name $processName -ErrorAction 'SilentlyContinue' - AfterAll { - Remove-Item $script:cmdProcess1FullPath -ErrorAction SilentlyContinue - Remove-Item $script:cmdProcess2FullPath -ErrorAction SilentlyContinue - } + if ($null -ne $process) + { + Stop-Process -Name $processName -ErrorAction 'SilentlyContinue' -Force + } + } + } + + Context 'Start two processes' { + $configurationName = 'StartProcessSet' - It "Ensure a set of processes is present" { - $configurationName = "EnsureProcessIsPresent" - $configurationPath = Join-Path -Path $TestDrive -ChildPath $configurationName - $errorPath = Join-Path -Path $TestDrive -ChildPath "StdErrorPath.txt" - $outputPath = Join-Path -Path $TestDrive -ChildPath "StdOutputPath.txt" + $processSetParameters = @{ + ProcessPaths = $script:processPaths + Ensure = 'Present' + } - try - { - Configuration $configurationName + foreach ($processPath in $processSetParameters.ProcessPaths) { - Import-DscResource -ModuleName xPSDesiredStateConfiguration + $processName = [System.IO.Path]::GetFileNameWithoutExtension($processPath) + $process = Get-Process -Name $processName -ErrorAction 'SilentlyContinue' - xProcessSet xProcessSet1 + if ($null -ne $process) { - Path = @($script:cmdProcess1FullPath, $script:cmdProcess2FullPath) - Ensure = "Present" - StandardErrorPath = $errorPath - StandardOutputPath = $outputPath + $null = Stop-Process -Name $processName -ErrorAction 'SilentlyContinue' -Force + + # May need to wait a moment for the correct state to populate + $millisecondsElapsed = 0 + $startTime = Get-Date + while ($null -eq $process -and $millisecondsElapsed -lt 3000) + { + $process = Get-Process -Name $processName -ErrorAction 'SilentlyContinue' + $millisecondsElapsed = ((Get-Date) - $startTime).TotalMilliseconds + } } - } - & $configurationName -OutputPath $configurationPath - - Start-DscConfiguration -Path $configurationPath -Wait -Force -Verbose + It "Should not have started process $processName before configuration" { + $process | Should Be $null + } + } - $process1 = Get-Process $script:cmdProcess1ShortName - $process1 | Should Not Be $null + It 'Should compile and run configuration' { + { + . $script:configurationFilePath -ConfigurationName $configurationName + & $configurationName -OutputPath $TestDrive @processSetParameters + Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force + } | Should Not Throw + } - $process2 = Get-Process $script:cmdProcess2ShortName - $process2 | Should Not Be $null - } - finally - { - if (Test-Path -Path $configurationPath) + foreach ($processPath in $processSetParameters.ProcessPaths) { - Remove-Item -Path $configurationPath -Recurse -Force + $processName = [System.IO.Path]::GetFileNameWithoutExtension($processPath) + $process = Get-Process -Name $processName -ErrorAction 'SilentlyContinue' + + It "Should have started process $processName after configuration" { + $process | Should Not Be $null + } } } - } - It "Ensure a set of processes is absent" { - $configurationName = "EnsureProcessIsAbsent" - $configurationPath = Join-Path -Path $TestDrive -ChildPath $configurationName + Context 'Stop two processes' { + $configurationName = 'StopProcessSet' - try - { - Configuration $configurationName + $processSetParameters = @{ + ProcessPaths = $script:processPaths + Ensure = 'Absent' + } + + foreach ($processPath in $processSetParameters.ProcessPaths) { - Import-DscResource -ModuleName xPSDesiredStateConfiguration + $processName = [System.IO.Path]::GetFileNameWithoutExtension($processPath) + $process = Get-Process -Name $processName -ErrorAction 'SilentlyContinue' - xProcessSet xProcessSet1 + if ($null -eq $process) { - Path = @($script:cmdProcess1FullPath, $script:cmdProcess2FullPath) - Ensure = "Absent" + $null = Start-Process -FilePath $processPath -ErrorAction 'SilentlyContinue' + + # May need to wait a moment for the correct state to populate + $millisecondsElapsed = 0 + $startTime = Get-Date + while ($null -eq $process -and $millisecondsElapsed -lt 3000) + { + $process = Get-Process -Name $processName -ErrorAction 'SilentlyContinue' + $millisecondsElapsed = ((Get-Date) - $startTime).TotalMilliseconds + } } - } - - & $configurationName -OutputPath $configurationPath - Start-DscConfiguration -Path $configurationPath -Wait -Force -Verbose - - $process1 = Get-Process $script:cmdProcess1ShortName -ErrorAction SilentlyContinue - $process1 | Should Be $null + It "Should have started process $processName before configuration" { + $process | Should Not Be $null + } + } - $process2 = Get-Process $script:cmdProcess2ShortName -ErrorAction SilentlyContinue - $process2 | Should Be $null + It 'Should compile and run configuration' { + { + . $script:configurationFilePath -ConfigurationName $configurationName + & $configurationName -OutputPath $TestDrive @processSetParameters + Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force + } | Should Not Throw + } - } - finally - { - if (Test-Path -path $configurationPath) + foreach ($processPath in $processSetParameters.ProcessPaths) { - Remove-Item -Path $configurationPath -Recurse -Force + $processName = [System.IO.Path]::GetFileNameWithoutExtension($processPath) + $process = Get-Process -Name $processName -ErrorAction 'SilentlyContinue' + + It "Should have stopped process $processName after configuration" { + $process | Should Be $null + } } } } } +finally +{ + Exit-DscResourceTestEnvironment -TestEnvironment $script:testEnvironment +} diff --git a/Tests/Integration/xProcessSet.config.ps1 b/Tests/Integration/xProcessSet.config.ps1 new file mode 100644 index 000000000..b3c934638 --- /dev/null +++ b/Tests/Integration/xProcessSet.config.ps1 @@ -0,0 +1,29 @@ +param +( + [Parameter(Mandatory = $true)] + [String] + $ConfigurationName +) + +Configuration $ConfigurationName +{ + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String[]] + $ProcessPaths, + + [ValidateSet('Present', 'Absent')] + [String] + $Ensure = 'Present' + ) + + Import-DscResource -ModuleName 'xPSDesiredStateConfiguration' + + xProcessSet xProcessSet1 + { + Path = $ProcessPaths + Ensure = $Ensure + } +} diff --git a/Tests/WindowsProcessTestProcess.cs b/Tests/WindowsProcessTestProcess.cs index 04e68fc88..ceb28b52e 100644 --- a/Tests/WindowsProcessTestProcess.cs +++ b/Tests/WindowsProcessTestProcess.cs @@ -12,9 +12,6 @@ static void Main(string[] args) if (args.Length > 0) { - // and that it is a path - - // a second argument for infinite wait string filePath = args[0]; using (StreamWriter outputFile = new StreamWriter(filePath)) @@ -25,11 +22,12 @@ static void Main(string[] args) outputFile.WriteLine(line); } } - if (args.Length == 1 || args[1] != "Stop Running") - { - // Sleep so that the process stays running until it is killed - Thread.Sleep(Timeout.Infinite); - } + } + + if (args.Length <= 1 || (args.Length > 1 && args[1] != "Stop Running")) + { + // Sleep so that the process stays running until it is killed + Thread.Sleep(Timeout.Infinite); } } } diff --git a/Tests/WindowsProcessTestProcessSet.exe b/Tests/WindowsProcessTestProcessSet.exe new file mode 100644 index 0000000000000000000000000000000000000000..9340a2daa18e5e5bde9f1be647309668026f2e25 GIT binary patch literal 5632 zcmeHLU2Ggz6+ScG^>4h%#)*GuQYPy}a)`b0U()>8iGS7!OXJvNH+Bn_lG)ko^(3=1 z)48*bmxn5Vij;?j6!HL3d7wgy3KHr=1r<;sKpve3G zR7icOdN*_Kx#ygF{_fw2XTNZV8i*){@9|@zchHMAA>jvun_wqC{gVW}-SXanca#h7 z9mubkTys6AH6>I(;(tLS*d$ zrKn9ABx(gkd}y~118Ddr@Cl%yQv<(&QKJB&-NXmFXbp7r6{218-+CVo0}&d7-Z_C! z5cSuTgiO(0CE9^5)@2+T)&q zk@Rf>h)^mVs%sPQKJ78ffuLkv1yktSgRibl5KYw$CF+X2q6yzric|LiqQ4^#4HzkP zX_Z1x0iGF*(RUz+xk>7IbvLj>ogPN+8;82QF4=c`1EOzLZ+E`m(ALyHLu%)}9nE|v zvd|8j9kI@i_>t~pEE&Jo(MT=HMhA6@6^NNdyv`eO+nNa!kpGA7|=3BNAsMaqLdCE>3m<@@w| zrICK2J_hVnTcPcdBJe?pA0&bAr(a7K0zZPx?V^7u_ti=GsL;FyXX!O+RL3ydaX^f| z)_oe5g2>)bTj;c^VABX%4`3@D2HZtM5}uTBQo^i+pO^4O33b4|WJ>%c39Ay`lJMI! zi2Q$-PQmln=(lPgy-EAkG<_M|MoMGxrvN+XdBA7r8elJRz$5exz!3>gOE@j*&(pU+ z&q>M`B{b+QP_EOT06G1YK1H#c(b^zT+3q7yamz++Wc_%K%{s-3#m-Q!%6(Q&XXmKQ z4ac+00_9gcrWYxjF}dq-y!Fl|Nwk)l41*@7j{d&f z+Oe0-QpMB#E!i2y4bOBpGU2ytT5`rEX6b7Z6(4Zg5yK80UG${SZyFH5M&huw1wWB0{J$9cD#x~UaIIKZ(^J8%@f0U z@JYjcX_Af6(3aHUEvX|!Jvq#F$c2VWRDlLt>M6-X^yn*J%{}+eAI!Y_;-Nb;ugw33 zVw$2fH)y29fh3b+m=Hj1Z))D3P!qdn_9vPXYMfLxk%$q5n}7VJrG=i+2e;xFfvJR# z_Os&7%B!7=p6*_B?3p!#$&HES#THN?*CcN`$n|WW7m7=vE`@g2wxRaRH#Kc|aOgPE zVTBGp$Jj~Z`0>JkF|s^7Fgkqv*g&B$$_AJ*q_ffCX+9){zIJvFAdA}qan5l429n5ES?nv@Q&qp1E8xA8uBt@Yu%(XB`S5kXC#TDr_}(3i@30bBDrOJADF%+6iREUlUxJ3!-p zrD!@L(ql{5>0*QMrEOF70UW1rZAos&?Pbz#u@J`bQFg_!b`myU7Puy`%`K|20?w6b z$D3JW^0>=ep0RY%lJx{VK1ABYW(6M^MU{1mhAO%nHs^zCy3Ts=uAkU~E`)B@Nd{;j zzp{xXj|KJPIlMDL^m@bk#Mc`}F<8LsdIfL>r#?qnROlBm&H|nbs&#rd_VE4Sg$;B? zukG1Tm3V0*PNY2}X$z#22R$b4BnF)}E#rm}?I4L+d2n@5Irutm9}`?#@`>Jx{Tl~F zflof9#En+kLX)Tk71SKjZ#NI%>SelAMSim-D7`sX3xkRhcxq6=>1jZ}hv&F~=7y#Of>bJt@ZLS-{p`v|@~ z3we<}U&`9>)vELO!~2kio;7^p|027g`vUNi)G9LOQdLG;q7_)g{j-Utp{mpH8-|}l zgzFV`yvX;uo?vB*&|8LH5A|A-aBaz$~3K}EL&u#;r!|bpTxVF z(ri3|~lY?WD>CE$2U=UIEzxz4 zFes_y=oa2Yt{>gt``FQ15+E>kG+LoE*!Z8ck7ow7;O`rE Date: Thu, 15 Dec 2016 18:53:48 -0800 Subject: [PATCH 4/4] Update documentation per code review --- .../xWindowsFeatureSet/xWindowsFeatureSet.schema.psm1 | 2 +- README.md | 6 +++--- .../xWindowsOptionalFeatureSet.Integration.Tests.ps1 | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/DSCResources/xWindowsFeatureSet/xWindowsFeatureSet.schema.psm1 b/DSCResources/xWindowsFeatureSet/xWindowsFeatureSet.schema.psm1 index c91f84112..017b3a661 100644 --- a/DSCResources/xWindowsFeatureSet/xWindowsFeatureSet.schema.psm1 +++ b/DSCResources/xWindowsFeatureSet/xWindowsFeatureSet.schema.psm1 @@ -27,7 +27,7 @@ Import-Module -Name $script:resourceSetHelperFilePath If Ensure is set to Absent, all subfeatures will be uninstalled. .PARAMETER Credential - The credential of the user account under which to install or uninstall the role or feature. + The credential of the user account under which to install or uninstall the roles or features. .PARAMETER LogPath The custom file path to which to log this operation. diff --git a/README.md b/README.md index 06311ce92..f9ee02183 100644 --- a/README.md +++ b/README.md @@ -432,10 +432,10 @@ Provides a mechanism to configure and manage multiple xWindowsFeature resources #### Parameters -* **[String] Name** _(Key)_: The names of the roles or features that you want install or uninstall. This may be different from the display name of the feature/role. To retrieve the names of features/roles on a machine use the Get-WindowsFeature cmdlet. +* **[String] Name** _(Key)_: The names of the roles or features to install or uninstall. This may be different from the display name of the feature/role. To retrieve the names of features/roles on a machine use the Get-WindowsFeature cmdlet. * **[String] Ensure** _(Write)_: Specifies whether the feature should be installed or uninstalled. To install features, set this property to Present. To uninstall features, set this property to Absent. { Present | Absent }. * **[Boolean] IncludeAllSubFeature** _(Write)_: Specifies whether or not all subfeatures should be installed or uninstalled alongside the specified roles or features. If this property is true and Ensure is set to Present, all subfeatures will be installed. If this property is false and Ensure is set to Present, subfeatures will not be installed or uninstalled. If Ensure is set to Absent, all subfeatures will be uninstalled. -* **[PSCredential] Credential** _(Write)_: The credential of the user account under which to install or uninstall the role or feature. +* **[PSCredential] Credential** _(Write)_: The credential of the user account under which to install or uninstall the roles or features. * **[String] LogPath** _(Write)_: The custom file path to which to log this operation. If not passed in, the default log path will be used (%windir%\logs\ServerManager.log). #### Read-Only Properties from Get-TargetResource @@ -492,7 +492,7 @@ This resource works on Nano Server. The following parameters will be the same for each Windows optional feature in the set: -* **[String] Ensure** _(Write)_: Specifies whether the Windows optional features should be enabled or disabled. To enable the features, set this property to Present. To disable the features, set this property to Absent. { *Present* | Absent }. +* **[String] Ensure** _(Write)_: Specifies whether the Windows optional features should be enabled or disabled. To enable the features, set this property to Present. To disable the features, set this property to Absent. { Present | Absent }. * **[Boolean] RemoveFilesOnDisable** _(Write)_: Specifies whether or not to remove the files associated with the Windows optional features when they are disabled. * **[Boolean] NoWindowsUpdateCheck** _(Write)_: Specifies whether or not DISM should contact Windows Update (WU) when searching for the source files to restore Windows optional features on an online image. * **[String] LogPath** _(Write)_: The file path to which to log the operation. diff --git a/Tests/Integration/xWindowsOptionalFeatureSet.Integration.Tests.ps1 b/Tests/Integration/xWindowsOptionalFeatureSet.Integration.Tests.ps1 index dabb768b9..8970ebc55 100644 --- a/Tests/Integration/xWindowsOptionalFeatureSet.Integration.Tests.ps1 +++ b/Tests/Integration/xWindowsOptionalFeatureSet.Integration.Tests.ps1 @@ -97,6 +97,7 @@ try foreach ($windowsOptionalFeatureName in $wofSetParameters.WindowsOptionalFeatureNames) { $windowsOptionalFeature = Dism\Get-WindowsOptionalFeature -Online -FeatureName $windowsOptionalFeatureName + It "Should be able to retrieve Windows optional feature $windowsOptionalFeatureName after the configuration" { $windowsOptionalFeature | Should Not Be $null } @@ -172,6 +173,7 @@ try foreach ($windowsOptionalFeatureName in $wofSetParameters.WindowsOptionalFeatureNames) { $windowsOptionalFeature = Dism\Get-WindowsOptionalFeature -Online -FeatureName $windowsOptionalFeatureName + It "Should be able to retrieve Windows optional feature $windowsOptionalFeatureName after the confguration" { $windowsOptionalFeature | Should Not Be $null }