diff --git a/CHANGELOG.md b/CHANGELOG.md index 87cda7e2..49f5fe6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 that _Microsoft DNS Server_ is required on a node targeted by a resource, and that the DSC resources requires the [DnsServer](https://docs.microsoft.com/en-us/powershell/module/dnsserver) PowerShell module ([issue #37](https://github.com/dsccommunity/xDnsServer/issues/37)). + - Added the base class `ResourcePropertiesBase` to hold DSC properties that + can be inherited for all class-based resources. + - Added the base class `ResourceBase` to hold methods that should be + inherited for all class-based resources. + - Added new private function `ConvertTo-TimeSpan` to help when evaluating + properties that must be passed as strings and then converted to `[System.TimeSpan]`. + - Added `prefix.ps1` that is used to import dependent modules like _DscResource.Common_. + - Added new resource + - _DnsServerScavenging_ - resource to enforce scavenging settings ([issue #189](https://github.com/dsccommunity/xDnsServer/issues/189)). - xDNSServerClientSubnet - Added integration tests. - xDnsServerPrimaryZone @@ -73,6 +82,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Only add required role in integration tests pipeline. - Updated the pipeline to use new deploy tasks. - Revert back to using the latest version of module Sampler for the pipeline ([issue #211](https://github.com/dsccommunity/xDnsServer/issues/211)). +- DnsRecordBase + - Changed class to inherit properties from 'ResourcePropertiesBase`. - xDnsRecordSrv - Now uses `[CimInstance]::new()` both in the resource code and the resource unit test to clone the existing DNS record instead of using the method diff --git a/build.yaml b/build.yaml index 35e2b492..d151dc4c 100644 --- a/build.yaml +++ b/build.yaml @@ -8,6 +8,7 @@ CopyPaths: - Modules Encoding: UTF8 VersionedOutputDirectory: true +Prefix: prefix.ps1 #################################################### # ModuleBuilder Dependent Modules Configuration # diff --git a/source/Classes/001.ResourceBase.ps1 b/source/Classes/001.ResourceBase.ps1 new file mode 100644 index 00000000..8443d3f9 --- /dev/null +++ b/source/Classes/001.ResourceBase.ps1 @@ -0,0 +1,110 @@ +<# + .SYNOPSIS + A class with methods that are equal for all class-based resources. + + .DESCRIPTION + A class with methods that are equal for all class-based resources. + + .NOTES + This class should not contain any DSC properties. +#> + +class ResourceBase +{ + # Hidden property for holding localization strings + hidden [System.Collections.Hashtable] $localizedData = @{} + + # Default constructor + ResourceBase() + { + Assert-Module -ModuleName 'DnsServer' + + $localizedDataFileName = ('{0}.strings.psd1' -f $this.GetType().Name) + + $this.localizedData = Get-LocalizedData -DefaultUICulture 'en-US' -FileName $localizedDataFileName + } + + [ResourceBase] Get([Microsoft.Management.Infrastructure.CimInstance] $CommandProperties) + { + $dscResourceObject = [System.Activator]::CreateInstance($this.GetType()) + + foreach ($propertyName in $this.PSObject.Properties.Name) + { + if ($propertyName -in @($CommandProperties.PSObject.Properties.Name)) + { + $dscResourceObject.$propertyName = $CommandProperties.$propertyName + } + } + + # Always set this as it won't be in the $CommandProperties + $dscResourceObject.DnsServer = $this.DnsServer + + return $dscResourceObject + } + + [void] Set() + { + } + + [System.Boolean] Test() + { + $isInDesiredState = $true + + <# + Returns all enforced properties not in desires state, or $null if + all enforced properties are in desired state. + #> + $propertiesNotInDesiredState = $this.Compare() + + if ($propertiesNotInDesiredState) + { + $isInDesiredState = $false + } + + return $isInDesiredState + } + + # Returns a hashtable containing all properties that should be enforced. + hidden [System.Collections.Hashtable[]] Compare() + { + $currentState = $this.Get() | ConvertTo-HashTableFromObject + $desiredState = $this | ConvertTo-HashTableFromObject + + # Remove properties that have $null as the value. + @($desiredState.Keys) | ForEach-Object -Process { + $isReadProperty = $this.GetType().GetMember($_).CustomAttributes.Where( { $_.NamedArguments.MemberName -eq 'NotConfigurable' }).NamedArguments.TypedValue.Value -eq $true + + # Also remove read properties so that there is no chance to campare those. + if ($isReadProperty -or $null -eq $desiredState[$_]) + { + $desiredState.Remove($_) + } + } + + $CompareDscParameterState = @{ + CurrentValues = $currentState + DesiredValues = $desiredState + Properties = $desiredState.Keys + ExcludeProperties = @('DnsServer') + IncludeValue = $true + } + + <# + Returns all enforced properties not in desires state, or $null if + all enforced properties are in desired state. + #> + return (Compare-DscParameterState @CompareDscParameterState) + } + + # Returns a hashtable containing all properties that should be enforced. + hidden [System.Collections.Hashtable] GetDesiredStateForSplatting([System.Collections.Hashtable[]] $Properties) + { + $desiredState = @{} + + $Properties | ForEach-Object -Process { + $desiredState[$_.Property] = $_.ExpectedValue + } + + return $desiredState + } +} diff --git a/source/Classes/001.ResourcePropertiesBase.ps1 b/source/Classes/001.ResourcePropertiesBase.ps1 new file mode 100644 index 00000000..738ded60 --- /dev/null +++ b/source/Classes/001.ResourcePropertiesBase.ps1 @@ -0,0 +1,18 @@ +<# + .SYNOPSIS + A class with DSC properties that are equal for all class-based resources. + + .DESCRIPTION + A class with DSC properties that are equal for all class-based resources. + + .PARAMETER DnsServer + The host name of the Domain Name System (DNS) server, or use 'localhost' + for the current node. Defaults to `'localhost'`. +#> + +class ResourcePropertiesBase +{ + [DscProperty()] + [System.String] + $DnsServer = 'localhost' +} diff --git a/source/Classes/001.DnsRecordBase.ps1 b/source/Classes/002.DnsRecordBase.ps1 similarity index 98% rename from source/Classes/001.DnsRecordBase.ps1 rename to source/Classes/002.DnsRecordBase.ps1 index 13b72a35..841c7170 100644 --- a/source/Classes/001.DnsRecordBase.ps1 +++ b/source/Classes/002.DnsRecordBase.ps1 @@ -11,14 +11,11 @@ .PARAMETER TimeToLive Specifies the TimeToLive value of the SRV record. Value must be in valid TimeSpan string format (i.e.: Days.Hours:Minutes:Seconds.Miliseconds or 30.23:59:59.999). - .PARAMETER DnsServer - Name of the DnsServer on which to create the record. - .PARAMETER Ensure Whether the host record should be present or removed. #> -class DnsRecordBase +class DnsRecordBase : ResourcePropertiesBase { [DscProperty(Key)] [System.String] @@ -28,10 +25,6 @@ class DnsRecordBase [System.String] $TimeToLive - [DscProperty()] - [System.String] - $DnsServer = 'localhost' - [DscProperty()] [Ensure] $Ensure = [Ensure]::Present diff --git a/source/Classes/002.DnsRecordA.ps1 b/source/Classes/003.DnsRecordA.ps1 similarity index 100% rename from source/Classes/002.DnsRecordA.ps1 rename to source/Classes/003.DnsRecordA.ps1 diff --git a/source/Classes/002.DnsRecordSrv.ps1 b/source/Classes/003.DnsRecordSrv.ps1 similarity index 100% rename from source/Classes/002.DnsRecordSrv.ps1 rename to source/Classes/003.DnsRecordSrv.ps1 diff --git a/source/Classes/003.DnsServerScavenging.ps1 b/source/Classes/003.DnsServerScavenging.ps1 new file mode 100644 index 00000000..05fbb26b --- /dev/null +++ b/source/Classes/003.DnsServerScavenging.ps1 @@ -0,0 +1,185 @@ +<# + .SYNOPSIS + The DnsServerScavenging DSC resource manages scavenging on a Microsoft + Domain Name System (DNS) server. + + .DESCRIPTION + The DnsServerScavenging DSC resource manages scavenging on a Microsoft + Domain Name System (DNS) server. + + .PARAMETER DnsServer + The host name of the Domain Name System (DNS) server, or use 'localhost' + for the current node. + + .PARAMETER ScavengingState + Specifies whether to Enable automatic scavenging of stale records. + `ScavengingState` determines whether the DNS scavenging feature is enabled + by default on newly created zones. + + .PARAMETER ScavengingInterval + Specifies a length of time as a value that can be converted to a `[TimeSpan]` + object. `ScavengingInterval` determines whether the scavenging feature for + the DNS server is enabled and sets the number of hours between scavenging + cycles. The value `0` disables scavenging for the DNS server. A setting + greater than `0` enables scavenging for the server and sets the number of + days, hours, minutes, and seconds (formatted as dd.hh:mm:ss) between + scavenging cycles. The minimum value is 0. The maximum value is 365.00:00:00 + (1 year). + + .PARAMETER RefreshInterval + Specifies the refresh interval as a value that can be converted to a `[TimeSpan]` + object (formatted as dd.hh:mm:ss). During this interval, a DNS server can + refresh a resource record that has a non-zero time stamp. Zones on the server + inherit this value automatically. If a DNS server does not refresh a resource + record that has a non-zero time stamp, the DNS server can remove that record + during the next scavenging. Do not select a value smaller than the longest + refresh period of a resource record registered in the zone. The minimum value + is `0`. The maximum value is 365.00:00:00 (1 year). + + .PARAMETER NoRefreshInterval + Specifies a length of time as a value that can be converted to a `[TimeSpan]` + object (formatted as dd.hh:mm:ss). `NoRefreshInterval` sets a period of time + in which no refreshes are accepted for dynamically updated records. Zones on + the server inherit this value automatically. This value is the interval between + the last update of a timestamp for a record and the earliest time when the + timestamp can be refreshed. The minimum value is 0. The maximum value is + 365.00:00:00 (1 year). + + .PARAMETER LastScavengeTime + The time when the last scavenging cycle was executed. +#> + +[DscResource()] +class DnsServerScavenging : ResourceBase +{ + [DscProperty(Key)] + [System.String] + $DnsServer + + [DscProperty()] + [Nullable[System.Boolean]] + $ScavengingState + + [DscProperty()] + [System.String] + $ScavengingInterval + + [DscProperty()] + [System.String] + $RefreshInterval + + [DscProperty()] + [System.String] + $NoRefreshInterval + + [DscProperty(NotConfigurable)] + [Nullable[System.DateTime]] + $LastScavengeTime + + [DnsServerScavenging] Get() + { + Write-Verbose -Message ($this.localizedData.GetCurrentState -f $this.DnsServer) + + $getDnsServerScavengingParameters = @{} + + if ($this.DnsServer -ne 'localhost') + { + $getDnsServerScavengingParameters['ComputerName'] = $this.DnsServer + } + + $getDnsServerScavengingResult = Get-DnsServerScavenging @getDnsServerScavengingParameters + + # Call the base method to return the properties. + return ([ResourceBase] $this).Get($getDnsServerScavengingResult) + } + + [void] Set() + { + $this.AssertProperties() + + Write-Verbose -Message ($this.localizedData.SetDesiredState -f $this.DnsServer) + + # Call the base method to get enforced properties that are not in desired state. + $propertiesNotInDesiredState = $this.Compare() + + if ($propertiesNotInDesiredState) + { + $setDnsServerScavengingParameters = $this.GetDesiredStateForSplatting($propertiesNotInDesiredState) + + $setDnsServerScavengingParameters.Keys | ForEach-Object -Process { + Write-Verbose -Message ($this.localizedData.SetProperty -f $_, $setDnsServerScavengingParameters.$_) + } + + if ($this.DnsServer -ne 'localhost') + { + $setDnsServerScavengingParameters['ComputerName'] = $this.DnsServer + } + + Set-DnsServerScavenging @setDnsServerScavengingParameters + } + else + { + Write-Verbose -Message $this.localizedData.NoPropertiesToSet + } + } + + [System.Boolean] Test() + { + $this.AssertProperties() + + Write-Verbose -Message ($this.localizedData.TestDesiredState -f $this.DnsServer) + + # Call the base method to test all of the properties that should be enforced. + $isInDesiredState = ([ResourceBase] $this).Test() + + if ($isInDesiredState) + { + Write-Verbose -Message ($this.localizedData.InDesiredState -f $this.DnsServer) + } + else + { + Write-Verbose -Message ($this.localizedData.NotInDesiredState -f $this.DnsServer) + } + + return $isInDesiredState + } + + hidden [void] AssertProperties() + { + @( + 'ScavengingInterval' + 'RefreshInterval' + 'NoRefreshInterval' + ) | ForEach-Object -Process { + $valueToConvert = $this.$_ + + # Only evaluate properties that have a value. + if ($null -ne $valueToConvert) + { + $timeSpanObject = $valueToConvert | ConvertTo-TimeSpan + + # If the conversion fails $null is returned. + if ($null -eq $timeSpanObject) + { + $errorMessage = $this.localizedData.PropertyHasWrongFormat -f $_, $valueToConvert + + New-InvalidOperationException -Message $errorMessage + } + + if ($timeSpanObject -gt [System.TimeSpan] '365.00:00:00') + { + $errorMessage = $this.localizedData.TimeSpanExceedMaximumValue -f $_, $timeSpanObject.ToString() + + New-InvalidOperationException -Message $errorMessage + } + + if ($timeSpanObject -lt [System.TimeSpan] '0.00:00:00') + { + $errorMessage = $this.localizedData.TimeSpanBelowMinimumValue -f $_, $timeSpanObject.ToString() + + New-InvalidOperationException -Message $errorMessage + } + } + } + } +} diff --git a/source/Classes/003.DnsRecordAScoped.ps1 b/source/Classes/004.DnsRecordAScoped.ps1 similarity index 100% rename from source/Classes/003.DnsRecordAScoped.ps1 rename to source/Classes/004.DnsRecordAScoped.ps1 diff --git a/source/Classes/003.DnsRecordSrvScoped.ps1 b/source/Classes/004.DnsRecordSrvScoped.ps1 similarity index 100% rename from source/Classes/003.DnsRecordSrvScoped.ps1 rename to source/Classes/004.DnsRecordSrvScoped.ps1 diff --git a/source/Examples/Resources/DnsServerScavenging/1-EnableAndChangeScavengingIntervals_Config.ps1 b/source/Examples/Resources/DnsServerScavenging/1-EnableAndChangeScavengingIntervals_Config.ps1 new file mode 100644 index 00000000..ed5f8a6f --- /dev/null +++ b/source/Examples/Resources/DnsServerScavenging/1-EnableAndChangeScavengingIntervals_Config.ps1 @@ -0,0 +1,57 @@ +<#PSScriptInfo + +.VERSION 1.0.0 + +.GUID 74a3fe1e-4094-4c78-b815-154c1907e54d + +.AUTHOR DSC Community + +.COMPANYNAME DSC Community + +.COPYRIGHT DSC Community contributors. All rights reserved. + +.TAGS DSCConfiguration + +.LICENSEURI https://github.com/dsccommunity/xDnsServer/blob/main/LICENSE + +.PROJECTURI https://github.com/dsccommunity/xDnsServer + +.ICONURI https://dsccommunity.org/images/DSC_Logo_300p.png + +.EXTERNALMODULEDEPENDENCIES + +.REQUIREDSCRIPTS + +.EXTERNALSCRIPTDEPENDENCIES + +.RELEASENOTES +Updated author, copyright notice, and URLs. + +.PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core + +#> + +#Requires -Module xDnsServer + +<# + .DESCRIPTION + This configuration will enable scavenging and change the scavenging intervals + on the DNS server. +#> + +Configuration EnableAndChangeScavengingIntervals_Config +{ + Import-DscResource -ModuleName 'xDnsServer' + + Node localhost + { + DnsServerScavenging 'EnableScavengingAndChangeIntervals' + { + DnsServer = 'localhost' + ScavengingState = $true + ScavengingInterval = '7.00:00:00' + RefreshInterval = '7.00:00:00' + NoRefreshInterval = '7.00:00:00' + } + } +} diff --git a/source/Examples/Resources/DnsServerScavenging/2-EnableScavenging_Config.ps1 b/source/Examples/Resources/DnsServerScavenging/2-EnableScavenging_Config.ps1 new file mode 100644 index 00000000..2fc2e6f5 --- /dev/null +++ b/source/Examples/Resources/DnsServerScavenging/2-EnableScavenging_Config.ps1 @@ -0,0 +1,54 @@ +<#PSScriptInfo + +.VERSION 1.0.0 + +.GUID e3aeafd4-b41a-48e0-b9be-9b5c01f904d3 + +.AUTHOR DSC Community + +.COMPANYNAME DSC Community + +.COPYRIGHT DSC Community contributors. All rights reserved. + +.TAGS DSCConfiguration + +.LICENSEURI https://github.com/dsccommunity/xDnsServer/blob/main/LICENSE + +.PROJECTURI https://github.com/dsccommunity/xDnsServer + +.ICONURI https://dsccommunity.org/images/DSC_Logo_300p.png + +.EXTERNALMODULEDEPENDENCIES + +.REQUIREDSCRIPTS + +.EXTERNALSCRIPTDEPENDENCIES + +.RELEASENOTES +Updated author, copyright notice, and URLs. + +.PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core + +#> + +#Requires -Module xDnsServer + +<# + .DESCRIPTION + This configuration will enable scavenging on the DNS server, using + the default interval values. +#> + +Configuration EnableScavenging_Config +{ + Import-DscResource -ModuleName 'xDnsServer' + + Node localhost + { + DnsServerScavenging 'EnableScavenging' + { + DnsServer = 'localhost' + ScavengingState = $true + } + } +} diff --git a/source/Examples/Resources/DnsServerScavenging/3-ChangeScavengingIntervals_Config.ps1 b/source/Examples/Resources/DnsServerScavenging/3-ChangeScavengingIntervals_Config.ps1 new file mode 100644 index 00000000..ce3b3390 --- /dev/null +++ b/source/Examples/Resources/DnsServerScavenging/3-ChangeScavengingIntervals_Config.ps1 @@ -0,0 +1,56 @@ +<#PSScriptInfo + +.VERSION 1.0.0 + +.GUID f148c0eb-fb6e-4c31-bccb-5ed304545b0d + +.AUTHOR DSC Community + +.COMPANYNAME DSC Community + +.COPYRIGHT DSC Community contributors. All rights reserved. + +.TAGS DSCConfiguration + +.LICENSEURI https://github.com/dsccommunity/xDnsServer/blob/main/LICENSE + +.PROJECTURI https://github.com/dsccommunity/xDnsServer + +.ICONURI https://dsccommunity.org/images/DSC_Logo_300p.png + +.EXTERNALMODULEDEPENDENCIES + +.REQUIREDSCRIPTS + +.EXTERNALSCRIPTDEPENDENCIES + +.RELEASENOTES +Updated author, copyright notice, and URLs. + +.PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core + +#> + +#Requires -Module xDnsServer + +<# + .DESCRIPTION + This configuration will change scavenging intervals on the DNS server, but + does not enforce that scavenging is enabled. +#> + +Configuration ChangeScavengingIntervals_Config +{ + Import-DscResource -ModuleName 'xDnsServer' + + Node localhost + { + DnsServerScavenging 'ChangeScavengingIntervals' + { + DnsServer = 'localhost' + ScavengingInterval = '7.00:00:00' + RefreshInterval = '7.00:00:00' + NoRefreshInterval = '7.00:00:00' + } + } +} diff --git a/source/Examples/Resources/DnsServerScavenging/4-DisableScavenging_Config.ps1 b/source/Examples/Resources/DnsServerScavenging/4-DisableScavenging_Config.ps1 new file mode 100644 index 00000000..1e9bc2f7 --- /dev/null +++ b/source/Examples/Resources/DnsServerScavenging/4-DisableScavenging_Config.ps1 @@ -0,0 +1,53 @@ +<#PSScriptInfo + +.VERSION 1.0.0 + +.GUID 6b985cf2-8c93-47d7-9b90-31b28ec5147d + +.AUTHOR DSC Community + +.COMPANYNAME DSC Community + +.COPYRIGHT DSC Community contributors. All rights reserved. + +.TAGS DSCConfiguration + +.LICENSEURI https://github.com/dsccommunity/xDnsServer/blob/main/LICENSE + +.PROJECTURI https://github.com/dsccommunity/xDnsServer + +.ICONURI https://dsccommunity.org/images/DSC_Logo_300p.png + +.EXTERNALMODULEDEPENDENCIES + +.REQUIREDSCRIPTS + +.EXTERNALSCRIPTDEPENDENCIES + +.RELEASENOTES +Updated author, copyright notice, and URLs. + +.PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core + +#> + +#Requires -Module xDnsServer + +<# + .DESCRIPTION + This configuration will disable scavenging on the DNS server. +#> + +Configuration DisableScavenging_Config +{ + Import-DscResource -ModuleName 'xDnsServer' + + Node localhost + { + DnsServerScavenging 'DisableScavenging' + { + DnsServer = 'localhost' + ScavengingState = $false + } + } +} diff --git a/source/Private/ConvertTo-TimeSpan.ps1 b/source/Private/ConvertTo-TimeSpan.ps1 new file mode 100644 index 00000000..c93cb653 --- /dev/null +++ b/source/Private/ConvertTo-TimeSpan.ps1 @@ -0,0 +1,31 @@ +<# + .SYNOPSIS + Assert that the value provided can be converted to a TimeSpan object. + + .PARAMETER Value + The time value as a string that should be converted. + + .OUTPUTS + Returns an TimeSpan object containing the converted value, or $null if + conversion was not possible. +#> +function ConvertTo-TimeSpan +{ + [CmdletBinding()] + [OutputType([System.TimeSpan])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [System.String] + $Value + ) + + $timeSpan = New-TimeSpan + + if (-not [System.TimeSpan]::TryParse($Value, [ref] $timeSpan)) + { + $timeSpan = $null + } + + return $timeSpan +} diff --git a/source/en-US/DnsRecordBase.strings.psd1 b/source/en-US/DnsRecordBase.strings.psd1 index a56df1e6..fb9879b4 100644 --- a/source/en-US/DnsRecordBase.strings.psd1 +++ b/source/en-US/DnsRecordBase.strings.psd1 @@ -1,7 +1,7 @@ <# .SYNOPSIS The localized resource strings in English (en-US) for the - resource DnsRecordBase. + class DnsRecordBase. #> ConvertFrom-StringData @' diff --git a/source/en-US/DnsServerScavenging.strings.psd1 b/source/en-US/DnsServerScavenging.strings.psd1 new file mode 100644 index 00000000..f3e37469 --- /dev/null +++ b/source/en-US/DnsServerScavenging.strings.psd1 @@ -0,0 +1,18 @@ +<# + .SYNOPSIS + The localized resource strings in English (en-US) for the + resource DnsServerScavenging. +#> + +ConvertFrom-StringData @' + GetCurrentState = Getting the current state of the scavenging settings for the server '{0}'. (DSS0001) + TestDesiredState = Determining the current state of the scavenging settings for the server '{0}'. (DSS0002) + SetDesiredState = Setting the desired state for the scavenging settings for the server '{0}'. (DSS0003) + NotInDesiredState = The scavenging settings for the server '{0}' is not in desired state. (DSS0004) + InDesiredState = The scavenging settings for the server '{0}' is in desired state. (DSS0005) + SetProperty = The scavenging property '{0}' will be set to '{1}'. (DSS0006) + PropertyHasWrongFormat = The property '{0}' has the value '{1}' that cannot be converted to [System.TimeSpan]. (DSS0007) + TimeSpanExceedMaximumValue = The property '{0}' has the value '{1}' that exceeds the maximum value of '365.00:00:00'. (DSS0008) + NoPropertiesToSet = All properties are in desired state. (DSS0009) + TimeSpanBelowMinimumValue = The property '{0}' has the value '{1}' that is below the minimum value of '0.00:00:00'. (DSS0010) +'@ diff --git a/source/en-US/ResourcePropertiesBase.strings.psd1 b/source/en-US/ResourcePropertiesBase.strings.psd1 new file mode 100644 index 00000000..20d13e8a --- /dev/null +++ b/source/en-US/ResourcePropertiesBase.strings.psd1 @@ -0,0 +1,14 @@ +<# + .SYNOPSIS + The localized resource strings in English (en-US) for the + class ResourcePropertiesBase. + + .NOTES + This should normally not have any strings since the base class is only meant + to have DSC properties that can be inherited. Though this localized string + file exist so that it possible to recursively look for strings in all inherited + classes (base classes). +#> + +ConvertFrom-StringData @' +'@ diff --git a/source/prefix.ps1 b/source/prefix.ps1 new file mode 100644 index 00000000..d0b25ca5 --- /dev/null +++ b/source/prefix.ps1 @@ -0,0 +1,3 @@ +# Import nested, 'DscResource.Common' module +$script:dscResourceCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules\DscResource.Common' +Import-Module -Name $script:dscResourceCommonModulePath diff --git a/tests/Integration/Classes/DnsServerScavenging.Integration.Tests.ps1 b/tests/Integration/Classes/DnsServerScavenging.Integration.Tests.ps1 new file mode 100644 index 00000000..693a59b5 --- /dev/null +++ b/tests/Integration/Classes/DnsServerScavenging.Integration.Tests.ps1 @@ -0,0 +1,230 @@ +$script:dscModuleName = 'xDnsServer' +$script:dscResourceName = 'DnsServerScavenging' + +try +{ + Import-Module -Name DscResource.Test -Force -ErrorAction 'Stop' +} +catch [System.IO.FileNotFoundException] +{ + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -Tasks build" first.' +} + +$script:testEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType 'Integration' + +try +{ + $configFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dscResourceName).config.ps1" + . $configFile + + Describe "$($script:dscResourceName)_Integration" { + BeforeAll { + $resourceId = "[$($script:dscResourceName)]Integration_Test" + } + + $configurationName = "$($script:dscResourceName)_EnableScavenging_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.ScavengingState | Should -BeTrue + } + + It 'Should return ''True'' when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Wait-ForIdleLcm -Clear + + $configurationName = "$($script:dscResourceName)_SetAllIntervals_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.ScavengingState | Should -BeTrue + $resourceCurrentState.ScavengingInterval | Should -Be '30.00:00:00' + $resourceCurrentState.RefreshInterval | Should -Be '30.00:00:00' + $resourceCurrentState.NoRefreshInterval | Should -Be '30.00:00:00' + } + + It 'Should return ''True'' when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Wait-ForIdleLcm -Clear + + $configurationName = "$($script:dscResourceName)_SetOneInterval_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.ScavengingState | Should -BeTrue + $resourceCurrentState.ScavengingInterval | Should -Be '6.23:00:00' + $resourceCurrentState.RefreshInterval | Should -Be '30.00:00:00' + $resourceCurrentState.NoRefreshInterval | Should -Be '30.00:00:00' + } + + It 'Should return ''True'' when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Wait-ForIdleLcm -Clear + + $configurationName = "$($script:dscResourceName)_DisableScavenging_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.ScavengingState | Should -BeFalse + $resourceCurrentState.ScavengingInterval | Should -Be '6.23:00:00' + $resourceCurrentState.RefreshInterval | Should -Be '30.00:00:00' + $resourceCurrentState.NoRefreshInterval | Should -Be '30.00:00:00' + } + + It 'Should return ''True'' when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Wait-ForIdleLcm -Clear + } +} +finally +{ + Restore-TestEnvironment -TestEnvironment $script:testEnvironment +} diff --git a/tests/Integration/Classes/DnsServerScavenging.config.ps1 b/tests/Integration/Classes/DnsServerScavenging.config.ps1 new file mode 100644 index 00000000..20df7538 --- /dev/null +++ b/tests/Integration/Classes/DnsServerScavenging.config.ps1 @@ -0,0 +1,82 @@ +$ConfigurationData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + CertificateFile = $env:DscPublicCertificatePath + } + ) +} + +<# + .SYNOPSIS + Enables scavenging. +#> +configuration DnsServerScavenging_EnableScavenging_Config +{ + Import-DscResource -ModuleName 'xDnsServer' + + node $AllNodes.NodeName + { + DnsServerScavenging 'Integration_Test' + { + DnsServer = 'localhost' + ScavengingState = $true + } + } +} + +<# + .SYNOPSIS + Sets all intervals. +#> +configuration DnsServerScavenging_SetAllIntervals_Config +{ + Import-DscResource -ModuleName 'xDnsServer' + + node $AllNodes.NodeName + { + DnsServerScavenging 'Integration_Test' + { + DnsServer = 'localhost' + ScavengingInterval = '30.00:00:00' + RefreshInterval = '30.00:00:00' + NoRefreshInterval = '30.00:00:00' + } + } +} + +<# + .SYNOPSIS + Sets one interval. +#> +configuration DnsServerScavenging_SetOneInterval_Config +{ + Import-DscResource -ModuleName 'xDnsServer' + + node $AllNodes.NodeName + { + DnsServerScavenging 'Integration_Test' + { + DnsServer = 'localhost' + ScavengingInterval = '6.23:00:00' + } + } +} + +<# + .SYNOPSIS + Disables scavenging. +#> +configuration DnsServerScavenging_DisableScavenging_Config +{ + Import-DscResource -ModuleName 'xDnsServer' + + node $AllNodes.NodeName + { + DnsServerScavenging 'Integration_Test' + { + DnsServer = 'localhost' + ScavengingState = $false + } + } +} diff --git a/tests/Unit/Classes/DnsServerScavenging.Tests.ps1 b/tests/Unit/Classes/DnsServerScavenging.Tests.ps1 new file mode 100644 index 00000000..393eb73d --- /dev/null +++ b/tests/Unit/Classes/DnsServerScavenging.Tests.ps1 @@ -0,0 +1,413 @@ +$ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path +$ProjectName = ( + Get-ChildItem $ProjectPath\*\*.psd1 | Where-Object -FilterScript { + ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and + $( + try + { + Test-ModuleManifest $_.FullName -ErrorAction Stop + } + catch + { + $false + } + ) + } +).BaseName + +Import-Module $ProjectName + +Get-Module -Name 'DnsServer' -All | Remove-Module -Force +Import-Module -Name "$PSScriptRoot\..\Stubs\DnsServer.psm1" + +Describe 'DnsServerScavenging\Get()' -Tag 'Get' { + Context 'When the system is in the desired state' { + BeforeAll { + Mock -CommandName Assert-Module -ModuleName $ProjectName + Mock -CommandName Get-DnsServerScavenging -ModuleName $ProjectName -MockWith { + return New-CimInstance -ClassName 'DnsServerScavenging' -Namespace 'root/Microsoft/Windows/DNS' -ClientOnly -Property @{ + ScavengingState = $true + ScavengingInterval = '30.00:00:00' + RefreshInterval = '30.00:00:00' + NoRefreshInterval = '30.00:00:00' + LastScavengeTime = '2021-01-01 00:00:00' + } + } + } + + BeforeEach { + $mockDnsServerScavengingInstance = InModuleScope $ProjectName { + [DnsServerScavenging]::new() + } + } + + It 'Should have correct instantiated the resource class' { + $mockDnsServerScavengingInstance | Should -Not -BeNullOrEmpty + $mockDnsServerScavengingInstance.GetType().Name | Should -Be 'DnsServerScavenging' + } + + It 'Should return the correct values for the properties when DnsServer is set to ''''' -TestCases @( + @{ + HostName = 'localhost' + } + @{ + HostName = 'dns.company.local' + } + ) { + param + ( + $HostName + ) + + $mockDnsServerScavengingInstance.DnsServer = $HostName + + $getResult = $mockDnsServerScavengingInstance.Get() + + $getResult.DnsServer | Should -Be $HostName + $getResult.ScavengingState | Should -BeTrue + $getResult.ScavengingInterval | Should -Be '30.00:00:00' + $getResult.RefreshInterval | Should -Be '30.00:00:00' + $getResult.NoRefreshInterval | Should -Be '30.00:00:00' + + # Returns as a DateTime type and not a string. + $getResult.LastScavengeTime.ToString('yyyy-mm-dd HH:mm:ss') | Should -Be ([System.DateTime] '2021-01-01 00:00:00').ToString('yyyy-mm-dd HH:mm:ss') + + Assert-MockCalled -CommandName Get-DnsServerScavenging -ModuleName $ProjectName -Exactly -Times 1 -Scope It + } + } +} + +Describe 'DnsServerScavenging\Test()' -Tag 'Test' { + BeforeAll { + Mock -CommandName Assert-Module -ModuleName $ProjectName + } + + Context 'When providing an invalid interval' { + BeforeEach { + $mockDnsServerScavengingInstance = InModuleScope $ProjectName { + [DnsServerScavenging]::new() + } + } + + Context 'When the value is a string that cannot be converted to [System.TimeSpan]' { + It 'Should throw the correct error' { + $mockInvalidTime = '235.a:00:00' + + $mockDnsServerScavengingInstance.ScavengingInterval = $mockInvalidTime + + $mockExpectedErrorMessage = $mockDnsServerScavengingInstance.localizedData.PropertyHasWrongFormat -f 'ScavengingInterval', $mockInvalidTime + + { $mockDnsServerScavengingInstance.Test() } | Should -Throw $mockExpectedErrorMessage + } + } + + Context 'When the time exceeds maximum allowed value' { + It 'Should throw the correct error' { + $mockInvalidTime = '365.00:00:01' + + $mockDnsServerScavengingInstance.ScavengingInterval = $mockInvalidTime + + $mockExpectedErrorMessage = $mockDnsServerScavengingInstance.localizedData.TimeSpanExceedMaximumValue -f 'ScavengingInterval', $mockInvalidTime + + { $mockDnsServerScavengingInstance.Test() } | Should -Throw $mockExpectedErrorMessage + } + } + + Context 'When the time is below minimum allowed value' { + It 'Should throw the correct error' { + $mockInvalidTime = '-1.00:00:00' + + $mockDnsServerScavengingInstance.ScavengingInterval = $mockInvalidTime + + $mockExpectedErrorMessage = $mockDnsServerScavengingInstance.localizedData.TimeSpanBelowMinimumValue -f 'ScavengingInterval', $mockInvalidTime + + { $mockDnsServerScavengingInstance.Test() } | Should -Throw $mockExpectedErrorMessage + } + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + $mockDnsServerScavengingInstance = InModuleScope $ProjectName { + [DnsServerScavenging]::new() + } + + $mockDnsServerScavengingInstance.ScavengingState = $true + $mockDnsServerScavengingInstance.ScavengingInterval = '30.00:00:00' + $mockDnsServerScavengingInstance.RefreshInterval = '30.00:00:00' + $mockDnsServerScavengingInstance.NoRefreshInterval = '30.00:00:00' + $mockDnsServerScavengingInstance.LastScavengeTime = '2021-01-01 00:00:00' + + # Override Get() method + $mockDnsServerScavengingInstance | + Add-Member -Force -MemberType ScriptMethod -Name Get -Value { + return InModuleScope $ProjectName { + [DnsServerScavenging] @{ + DnsServer = 'localhost' + ScavengingState = $true + ScavengingInterval = '30.00:00:00' + RefreshInterval = '30.00:00:00' + NoRefreshInterval = '30.00:00:00' + LastScavengeTime = '2021-01-01 00:00:00' + } + } + } + } + + It 'Should return the $true' { + $getResult = $mockDnsServerScavengingInstance.Test() + + $getResult | Should -BeTrue + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + $testCases = @( + @{ + PropertyName = 'ScavengingState' + PropertyValue = $false + } + @{ + PropertyName = 'ScavengingInterval' + PropertyValue = '7.00:00:00' + } + @{ + PropertyName = 'RefreshInterval' + PropertyValue = '7.00:00:00' + } + @{ + PropertyName = 'NoRefreshInterval' + PropertyValue = '7.00:00:00' + } + ) + } + + BeforeEach { + $mockDnsServerScavengingInstance = InModuleScope $ProjectName { + [DnsServerScavenging]::new() + } + + # Override Get() method + $mockDnsServerScavengingInstance | + Add-Member -Force -MemberType ScriptMethod -Name Get -Value { + return InModuleScope $ProjectName { + [DnsServerScavenging] @{ + DnsServer = 'localhost' + ScavengingState = $true + ScavengingInterval = '30.00:00:00' + RefreshInterval = '30.00:00:00' + NoRefreshInterval = '30.00:00:00' + LastScavengeTime = '2021-01-01 00:00:00' + } + } + } + } + + It 'Should return the $false when property is not in desired state' -TestCases $testCases { + param + ( + $PropertyName, + $PropertyValue + ) + + $mockDnsServerScavengingInstance.$PropertyName = $PropertyValue + + $getResult = $mockDnsServerScavengingInstance.Test() + + $getResult | Should -BeFalse + } + } +} + +Describe 'DnsServerScavenging\Set()' -Tag 'Set' { + BeforeAll { + Mock -CommandName Assert-Module -ModuleName $ProjectName + } + + Context 'When providing an invalid interval' { + BeforeEach { + $mockDnsServerScavengingInstance = InModuleScope $ProjectName { + [DnsServerScavenging]::new() + } + } + + Context 'When the value is a string that cannot be converted to [System.TimeSpan]' { + It 'Should throw the correct error' { + $mockInvalidTime = '235.a:00:00' + + $mockDnsServerScavengingInstance.ScavengingInterval = $mockInvalidTime + + $mockExpectedErrorMessage = $mockDnsServerScavengingInstance.localizedData.PropertyHasWrongFormat -f 'ScavengingInterval', $mockInvalidTime + + { $mockDnsServerScavengingInstance.Test() } | Should -Throw $mockExpectedErrorMessage + } + } + + Context 'When the time exceeds maximum allowed value' { + It 'Should throw the correct error' { + $mockInvalidTime = '365.00:00:01' + + $mockDnsServerScavengingInstance.ScavengingInterval = $mockInvalidTime + + $mockExpectedErrorMessage = $mockDnsServerScavengingInstance.localizedData.TimeSpanExceedMaximumValue -f 'ScavengingInterval', $mockInvalidTime + + { $mockDnsServerScavengingInstance.Test() } | Should -Throw $mockExpectedErrorMessage + } + } + + Context 'When the time is below minimum allowed value' { + It 'Should throw the correct error' { + $mockInvalidTime = '-1.00:00:00' + + $mockDnsServerScavengingInstance.ScavengingInterval = $mockInvalidTime + + $mockExpectedErrorMessage = $mockDnsServerScavengingInstance.localizedData.TimeSpanBelowMinimumValue -f 'ScavengingInterval', $mockInvalidTime + + { $mockDnsServerScavengingInstance.Test() } | Should -Throw $mockExpectedErrorMessage + } + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + Mock -CommandName Set-DnsServerScavenging -ModuleName $ProjectName + + $testCases = @( + @{ + PropertyName = 'ScavengingState' + PropertyValue = $true + } + @{ + PropertyName = 'ScavengingInterval' + PropertyValue = '30.00:00:00' + } + @{ + PropertyName = 'RefreshInterval' + PropertyValue = '30.00:00:00' + } + @{ + PropertyName = 'NoRefreshInterval' + PropertyValue = '30.00:00:00' + } + ) + } + + BeforeEach { + $mockDnsServerScavengingInstance = InModuleScope $ProjectName { + [DnsServerScavenging]::new() + } + + $mockDnsServerScavengingInstance.DnsServer = 'localhost' + + # Override Get() method + $mockDnsServerScavengingInstance | + Add-Member -Force -MemberType ScriptMethod -Name Get -Value { + return InModuleScope $ProjectName { + [DnsServerScavenging] @{ + DnsServer = 'localhost' + ScavengingState = $true + ScavengingInterval = '30.00:00:00' + RefreshInterval = '30.00:00:00' + NoRefreshInterval = '30.00:00:00' + LastScavengeTime = '2021-01-01 00:00:00' + } + } + } + } + + It 'Should not call any mock to set a value for property ''''' -TestCases $testCases { + param + ( + $PropertyName, + $PropertyValue + ) + + $mockDnsServerScavengingInstance.$PropertyName = $PropertyValue + + { $mockDnsServerScavengingInstance.Set() } | Should -Not -Throw + + Assert-MockCalled -CommandName Set-DnsServerScavenging -ModuleName $ProjectName -Exactly -Times 0 -Scope It + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + Mock -CommandName Set-DnsServerScavenging -ModuleName $ProjectName + + $testCases = @( + @{ + PropertyName = 'ScavengingState' + PropertyValue = $false + } + @{ + PropertyName = 'ScavengingInterval' + PropertyValue = '7.00:00:00' + } + @{ + PropertyName = 'RefreshInterval' + PropertyValue = '7.00:00:00' + } + @{ + PropertyName = 'NoRefreshInterval' + PropertyValue = '7.00:00:00' + } + ) + } + + BeforeEach { + $mockDnsServerScavengingInstance = InModuleScope $ProjectName { + [DnsServerScavenging]::new() + } + + # Override Get() method + $mockDnsServerScavengingInstance | + Add-Member -Force -MemberType ScriptMethod -Name Get -Value { + return InModuleScope $ProjectName { + [DnsServerScavenging] @{ + DnsServer = 'localhost' + ScavengingState = $true + ScavengingInterval = '30.00:00:00' + RefreshInterval = '30.00:00:00' + NoRefreshInterval = '30.00:00:00' + LastScavengeTime = '2021-01-01 00:00:00' + } + } + } + } + + Context 'When parameter DnsServer is set to ''localhost''' { + It 'Should set the desired value for property ''''' -TestCases $testCases { + param + ( + $PropertyName, + $PropertyValue + ) + + $mockDnsServerScavengingInstance.DnsServer = 'localhost' + $mockDnsServerScavengingInstance.$PropertyName = $PropertyValue + + { $mockDnsServerScavengingInstance.Set() } | Should -Not -Throw + + Assert-MockCalled -CommandName Set-DnsServerScavenging -ModuleName $ProjectName -Exactly -Times 1 -Scope It + } + } + + Context 'When parameter DnsServer is set to ''dns.company.local''' { + It 'Should set the desired value for property ''''' -TestCases $testCases { + param + ( + $PropertyName, + $PropertyValue + ) + + $mockDnsServerScavengingInstance.DnsServer = 'dns.company.local' + $mockDnsServerScavengingInstance.$PropertyName = $PropertyValue + + { $mockDnsServerScavengingInstance.Set() } | Should -Not -Throw + + Assert-MockCalled -CommandName Set-DnsServerScavenging -ModuleName $ProjectName -Exactly -Times 1 -Scope It + } + } + } +} diff --git a/tests/Unit/Private/ConvertTo-TimeSpan.Tests.ps1 b/tests/Unit/Private/ConvertTo-TimeSpan.Tests.ps1 new file mode 100644 index 00000000..b6182621 --- /dev/null +++ b/tests/Unit/Private/ConvertTo-TimeSpan.Tests.ps1 @@ -0,0 +1,43 @@ +$ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path +$ProjectName = (Get-ChildItem $ProjectPath\*\*.psd1 | Where-Object -FilterScript { + ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and + $(try + { + Test-ModuleManifest $_.FullName -ErrorAction Stop + } + catch + { + $false + }) } +).BaseName + +Import-Module $ProjectName + +InModuleScope $ProjectName { + Describe 'ConvertTo-TimeSpan' -Tag 'Private' { + Context 'When converting a valid time' { + Context 'When passing value with named parameter' { + It 'Should return the correct value' { + $result = ConvertTo-TimeSpan -Value '234' + $result | Should -BeOfType [System.TimeSpan] + $result.Days | Should -Be '234' + } + } + + Context 'When passing value in pipeline' { + It 'Should return the correct value' { + $result = '234' | ConvertTo-TimeSpan + $result | Should -BeOfType [System.TimeSpan] + $result.Days | Should -Be '234' + } + } + } + + Context 'When converting a invalid string' { + It 'Should return $null' { + $result = ConvertTo-TimeSpan -Value '234a' + $result | Should -BeNullOrEmpty + } + } + } +}