diff --git a/DSCResources/MSFT_xDnsServerClientSubnet/MSFT_xDnsServerClientSubnet.psm1 b/DSCResources/MSFT_xDnsServerClientSubnet/MSFT_xDnsServerClientSubnet.psm1 new file mode 100644 index 00000000..636c4be3 --- /dev/null +++ b/DSCResources/MSFT_xDnsServerClientSubnet/MSFT_xDnsServerClientSubnet.psm1 @@ -0,0 +1,234 @@ +# Localized messages +data LocalizedData +{ + # culture="en-US" + ConvertFrom-StringData @' + GettingDnsServerClientSubnetMessage = Getting DNS Server Client Subnet '{0}'. + CreatingDnsServerClientSubnetMessage = Creating DNS Server Client Subnet '{0}' IPv4 '{1}' and/or IPv6 '{2}'. + UpdatingDnsServerClientSubnetMessage = Updating DNS Server Client Subnet '{0}' IPv4 '{1}' and/or IPv6 '{2}'. + RemovingDnsServerClientSubnetMessage = Removing DNS Server Client Subnet '{0}'. + NotDesiredPropertyMessage = DNS Server Client Subnet property '{0}' is not correct. Expected '{1}', actual '{2}' + InDesiredStateMessage = DNS Server Client Subnet '{0}' is in the desired state. + NotInDesiredStateMessage = DNS Server Client Subnet '{0}' is NOT in the desired state. +'@ +} + +<# + .SYNOPSIS + This will return the current state of the resource. + + .PARAMETER Name + Specifies the name of the client subnet. + + .PARAMETER IPv4Subnet + Specify an array (1 or more values) of IPv4 Subnet addresses in CIDR Notation. + + .PARAMETER IPv6Subnet + Specify an array (1 or more values) of IPv6 Subnet addresses in CIDR Notation. +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name + + ) + + Write-Verbose -Message ($LocalizedData.GettingDnsServerClientSubnetMessage -f $Name) + $record = Get-DnsServerClientSubnet -Name $Name -ErrorAction SilentlyContinue + + if ($null -eq $record) + { + return @{ + Name = $Name + IPv4Subnet = $null + IPv6Subnet = $null + Ensure = 'Absent' + } + } + + return @{ + Name = $record.Name + IPv4Subnet = $record.IPv4Subnet + IPv6Subnet = $record.IPv6Subnet + Ensure = 'Present' + } +} #end function Get-TargetResource + +<# + .SYNOPSIS + This will configure the resource. + + .PARAMETER Name + Specifies the name of the client subnet. + + .PARAMETER IPv4Subnet + Specify an array (1 or more values) of IPv4 Subnet addresses in CIDR Notation. + + .PARAMETER IPv6Subnet + Specify an array (1 or more values) of IPv6 Subnet addresses in CIDR Notation. +#> +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.String[]] + $IPv4Subnet, + + [Parameter()] + [System.String[]] + $IPv6Subnet, + + [Parameter()] + [ValidateSet('Present','Absent')] + [System.String] + $Ensure = 'Present' + ) + + $dnsServerClientSubnetParameters = @{ Name = $Name} + $clientSubnet = Get-DnsServerClientSubnet -Name $Name -ErrorAction SilentlyContinue + if ($Ensure -eq 'Present') + { + if ($IPv4Subnet) + { + $dnsServerClientSubnetParameters.Add('IPv4Subnet',$IPv4Subnet) + } + if ($IPv6Subnet) + { + $dnsServerClientSubnetParameters.Add('IPv6Subnet',$IPv6Subnet) + } + + if ($clientSubnet) + { + $dnsServerClientSubnetParameters.Add('Action', "REPLACE") + Write-Verbose -Message ($LocalizedData.UpdatingDnsServerClientSubnetMessage -f $Name, "$IPv4Subnet", "$IPv6Subnet") + Set-DnsServerClientSubnet @dnsServerClientSubnetParameters + } + else + { + Write-Verbose -Message ($LocalizedData.CreatingDnsServerClientSubnetMessage -f $Name, "$IPv4Subnet", "$IPv6Subnet") + Add-DnsServerClientSubnet @dnsServerClientSubnetParameters + } + } + elseif ($Ensure -eq 'Absent') + { + Write-Verbose -Message ($LocalizedData.RemovingDnsServerClientSubnetMessage -f $Name) + Remove-DnsServerClientSubnet -Name $Name + } +} #end function Set-TargetResource + +<# + .SYNOPSIS + This will return whether the resource is in desired state. + + .PARAMETER Name + Specifies the name of the client subnet. + + .PARAMETER IPv4Subnet + Specify an array (1 or more values) of IPv4 Subnet addresses in CIDR Notation. + + .PARAMETER IPv6Subnet + Specify an array (1 or more values) of IPv6 Subnet addresses in CIDR Notation. +#> +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.String[]] + $IPv4Subnet, + + [Parameter()] + [System.String[]] + $IPv6Subnet, + + [Parameter()] + [ValidateSet('Present','Absent')] + [System.String] + $Ensure = 'Present' + ) + + $result = Get-TargetResource -Name $Name + + if ($Ensure -ne $result.Ensure) + { + Write-Verbose -Message ($LocalizedData.NotDesiredPropertyMessage -f 'Ensure', $Ensure, $result.Ensure) + Write-Verbose -Message ($LocalizedData.NotInDesiredStateMessage -f $Name) + return $false + } + elseif ($Ensure -eq 'Present') + { + $IPv4SubnetResult = $result.IPv4Subnet + $IPv6SubnetResult = $result.IPv6Subnet + + if (($null -eq $IPv4Subnet) -and ($null -ne $IPv4SubnetResult)) + { + Write-Verbose -Message ($LocalizedData.NotDesiredPropertyMessage -f 'IPv4Subnet', "$IPv4Subnet", "$IPv4SubnetResult") + Write-Verbose -Message ($LocalizedData.NotInDesiredStateMessage -f $Name) + return $false + } + + if (($null -eq $IPv4SubnetResult) -and ($null -ne $IPv4Subnet)) + { + Write-Verbose -Message ($LocalizedData.NotDesiredPropertyMessage -f 'IPv4Subnet', "$IPv4Subnet", "$IPv4SubnetResult") + Write-Verbose -Message ($LocalizedData.NotInDesiredStateMessage -f $Name) + return $false + } + + if ($IPv4Subnet) + { + $IPv4Difference = Compare-Object -ReferenceObject $IPv4Subnet -DifferenceObject $IPv4SubnetResult + if ($IPv4Difference) + { + Write-Verbose -Message ($LocalizedData.NotDesiredPropertyMessage -f 'IPv4Subnet', "$IPv4Subnet", "$IPv4SubnetResult") + Write-Verbose -Message ($LocalizedData.NotInDesiredStateMessage -f $Name) + return $false + } + } + + if (($null -eq $IPv6Subnet) -and ($null -ne $IPv6SubnetResult)) + { + Write-Verbose -Message ($LocalizedData.NotDesiredPropertyMessage -f 'IPv6Subnet', "$IPv6Subnet", "$IPv6SubnetResult") + Write-Verbose -Message ($LocalizedData.NotInDesiredStateMessage -f $Name) + return $false + } + + if (($null -eq $IPv6SubnetResult) -and ($null -ne $IPv6Subnet)) + { + Write-Verbose -Message ($LocalizedData.NotDesiredPropertyMessage -f 'IPv6Subnet', "$IPv6Subnet", "$IPv6SubnetResult") + Write-Verbose -Message ($LocalizedData.NotInDesiredStateMessage -f $Name) + return $false + } + + if ($IPv6Subnet) + { + $IPv6Difference = Compare-Object -ReferenceObject $IPv6Subnet -DifferenceObject $IPv6SubnetResult + if ($IPv6Difference) + { + Write-Verbose -Message ($LocalizedData.NotDesiredPropertyMessage -f 'IPv6Subnet', "$IPv6Subnet", "$IPv6SubnetResult") + Write-Verbose -Message ($LocalizedData.NotInDesiredStateMessage -f $Name) + return $false + } + } + } + Write-Verbose -Message ($LocalizedData.InDesiredStateMessage -f $Name) + return $true +} #end function Test-TargetResource + +Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xDnsServerClientSubnet/MSFT_xDnsServerClientSubnet.schema.mof b/DSCResources/MSFT_xDnsServerClientSubnet/MSFT_xDnsServerClientSubnet.schema.mof new file mode 100644 index 00000000..41950b35 --- /dev/null +++ b/DSCResources/MSFT_xDnsServerClientSubnet/MSFT_xDnsServerClientSubnet.schema.mof @@ -0,0 +1,9 @@ +[ClassVersion("1.0.0.0"), FriendlyName("xDnsServerClientSubnet")] +class MSFT_xDnsServerClientSubnet : OMI_BaseResource +{ + [Key, Description("Specifies the name of the client subnet.")] string Name; + [Write, Description("Specify an array (1 or more values) of IPv4 Subnet addresses in CIDR Notation.")] string IPv4Subnet[]; + [Write, Description("Specify an array (1 or more values) of IPv6 Subnet addresses in CIDR Notation.")] string IPv6Subnet[]; + [Write, Description("Should this DNS server client subnet be present or absent"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; +}; + diff --git a/README.md b/README.md index 93d78729..0de3be89 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Please check out common DSC Resources [contributing guidelines](https://github.c * **xDnsRecord** This resource allows for the creation of IPv4 host (A) records, CNames, or PTRs against a specific zone on the DNS server. * **xDnsServerSetting** This resource manages the DNS sever settings/properties. * **xDnsServerDiagnostics** This resource manages the DNS server diagnostic settings/properties. +* **xDnsServerClientSubnet** This resource manages the DNS Client Subnets that are used in DNS Policies. ### xDnsServerForwarder @@ -204,6 +205,15 @@ Please check out common DSC Resources [contributing guidelines](https://github.c * **UseSystemEventLog**: Specifies whether the DNS server uses the system event log for logging. * **WriteThrough**: Specifies whether the DNS server logs write-throughs. +### xDnsServerClientSubnet + +Requires Windows Server 2016 onwards + +* **Name**: Specifies the name of the client subnet. +* **IPv4Subnet**: Specify an array (1 or more values) of IPv4 Subnet addresses in CIDR Notation. +* **IPv6Subnet**: Specify an array (1 of more values) of IPv6 Subnet addresses in CIDR Notation. +* **Ensure**: Whether the client subnet should be present or removed + ### xDnsServerRootHint * **IsSingleInstance**: Specifies the resource is a single instance, the value must be 'Yes' @@ -218,6 +228,7 @@ Please check out common DSC Resources [contributing guidelines](https://github.c * Copied enhancements to Test-DscParameterState from NetworkingDsc * Put the helper module to its own folder * Added xDnsServerRootHint resource +* Added xDnsServerClientSubnet resource ### 1.13.0.0 diff --git a/Tests/Unit/MSFT_xDnsServerClientSubnet.Tests.ps1 b/Tests/Unit/MSFT_xDnsServerClientSubnet.Tests.ps1 new file mode 100644 index 00000000..5d4afefb --- /dev/null +++ b/Tests/Unit/MSFT_xDnsServerClientSubnet.Tests.ps1 @@ -0,0 +1,302 @@ +#region HEADER +$script:DSCModuleName = 'xDnsServer' +$script:DSCResourceName = 'MSFT_xDnsServerClientSubnet' + +$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests')) +} + +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force + +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit +#endregion HEADER + +# Begin Testing +try +{ + #region Pester Tests + + InModuleScope $script:DSCResourceName { + #region Pester Test Initialization + $mocks = @{ + IPv4Present = { + [PSCustomObject]@{ + Name = 'ClientSubnetA' + IPv4Subnet = '10.1.1.0/24' + IPv6Subnet = $null + } + } + Absent = { } + IPv6Present = { + [PSCustomObject]@{ + Name = 'ClientSubnetB' + IPv4Subnet = $null + IPv6Subnet = 'db8::1/28' + } + } + BothPresent = { + [PSCustomObject]@{ + Name = 'ClientSubnetC' + IPv4Subnet = '10.1.1.0/24' + IPv6Subnet = 'db8::1/28' + } + } + GetIPv4Present = { + [PSCustomObject]@{ + Name = 'ClientSubnetA' + IPv4Subnet = '10.1.1.0/24' + IPv6Subnet = $null + } + } + GetIPv6Present = { + [PSCustomObject]@{ + Name = 'ClientSubnetB' + IPv4Subnet = $null + IPv6Subnet = 'db8::1/28' + Ensure = 'Present' + } + } + GetBothPresent = { + [PSCustomObject]@{ + Name = 'ClientSubnetC' + IPv4Subnet = '10.1.1.0/24' + IPv6Subnet = 'db8::1/28' + } + } + } + #endregion + + #region Function Get-TargetResource + Describe "MSFT_xDnsServerClientSubnet\Get-TargetResource" -Tag 'Get' { + Context 'When the system is in the desired state' { + It 'Should set Ensure to Present when the IPv4 client subnet is present' { + Mock -CommandName Get-DnsServerClientSubnet $mocks.IPv4Present + + $getTargetResourceResult = Get-TargetResource 'ClientSubnetA' + $getTargetResourceResult.Ensure | Should -Be 'Present' + $getTargetResourceResult.IPv4Subnet | Should -Be '10.1.1.0/24' + $getTargetResourceResult.IPv6Subnet | Should -BeNullOrEmpty + + Assert-MockCalled -CommandName Get-DnsServerClientSubnet -Exactly -Times 1 -Scope It + } + + It 'Should set Ensure to Present when the IPv6 client subnet is present' { + Mock -CommandName Get-DnsServerClientSubnet $mocks.IPv6Present + + $getTargetResourceResult = Get-TargetResource 'ClientSubnetB' + $getTargetResourceResult.Ensure | Should -Be 'Present' + $getTargetResourceResult.IPv4Subnet | Should -BeNullOrEmpty + $getTargetResourceResult.IPv6Subnet | Should -Be 'db8::1/28' + + Assert-MockCalled -CommandName Get-DnsServerClientSubnet -Exactly -Times 1 -Scope It + } + It 'Should set Ensure to Present when both client subnets are present' { + Mock -CommandName Get-DnsServerClientSubnet $mocks.BothPresent + + $getTargetResourceResult = Get-TargetResource 'ClientSubnetC' + $getTargetResourceResult.Ensure | Should -Be 'Present' + $getTargetResourceResult.IPv4Subnet | Should -Be '10.1.1.0/24' + $getTargetResourceResult.IPv6Subnet | Should -Be 'db8::1/28' + + Assert-MockCalled -CommandName Get-DnsServerClientSubnet -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is not in the desired state' { + It 'Should set Ensure to Absent when the IPv4 client subnet is not present' { + Mock -CommandName Get-DnsServerClientSubnet $mocks.Absent + + $getTargetResourceResult = Get-TargetResource 'ClientSubnetA' + $getTargetResourceResult.Ensure | Should -Be 'Absent' + $getTargetResourceResult.IPv4Subnet | Should -BeNullOrEmpty + $getTargetResourceResult.IPv6Subnet | Should -BeNullOrEmpty + + Assert-MockCalled -CommandName Get-DnsServerClientSubnet -Exactly -Times 1 -Scope It + } + + It 'Should set Ensure to Absent when the IPv6 client subnet is not present' { + Mock -CommandName Get-DnsServerClientSubnet $mocks.Absent + + $getTargetResourceResult = Get-TargetResource 'ClientSubnetB' + $getTargetResourceResult.Ensure | Should -Be 'Absent' + $getTargetResourceResult.IPv4Subnet | Should -BeNullOrEmpty + $getTargetResourceResult.IPv6Subnet | Should -BeNullOrEmpty + + Assert-MockCalled -CommandName Get-DnsServerClientSubnet -Exactly -Times 1 -Scope It + } + It 'Should set Ensure to Absent when both client subnets are not present' { + Mock -CommandName Get-DnsServerClientSubnet $mocks.Absent + + $getTargetResourceResult = Get-TargetResource 'ClientSubnetC' + $getTargetResourceResult.Ensure | Should -Be 'Absent' + $getTargetResourceResult.IPv4Subnet | Should -BeNullOrEmpty + $getTargetResourceResult.IPv6Subnet | Should -BeNullOrEmpty + + Assert-MockCalled -CommandName Get-DnsServerClientSubnet -Exactly -Times 1 -Scope It + } + } + } + #endregion Function Get-TargetResource + + #region Function Test-TargetResource + Describe "MSFT_xDnsServerClientSubnet\Test-TargetResource" -Tag 'Test' { + Context 'When the system is in the desired state' { + It 'Should return True when the IPv4Subnet matches' { + Mock -CommandName Get-DnsServerClientSubnet $mocks.IPv4Present + $params = @{ + Ensure = 'Present' + Name = 'ClientSubnetA' + IPv4Subnet = '10.1.1.0/24' + } + Test-TargetResource @params | Should -BeTrue + } + + It 'Should return True when the IPv6Subnet matches' { + Mock -CommandName Get-DnsServerClientSubnet $mocks.IPv6Present + $params = @{ + Ensure = 'Present' + Name = 'ClientSubnetB' + IPv6Subnet = 'db8::1/28' + } + Test-TargetResource @params | Should -BeTrue + } + + It 'Should return True when both IPv4 and IPv6 Subnets match' { + Mock -CommandName Get-DnsServerClientSubnet $mocks.BothPresent + $params = @{ + Ensure = 'Present' + Name = 'ClientSubnetC' + IPv4Subnet = '10.1.1.0/24' + IPv6Subnet = 'db8::1/28' + } + Test-TargetResource @params | Should -BeTrue + } + } + + Context 'When the system is not in the desired state' { + It 'Should return False when the Ensure doesnt match' { + Mock -CommandName Get-DnsServerClientSubnet $mocks.Absent + $params = @{ + Ensure = 'Present' + Name = 'ClientSubnetA' + IPv4Subnet = '10.1.20.0/24' + } + Test-TargetResource @params | Should -BeFalse + + Assert-MockCalled -CommandName Get-DnsServerClientSubnet -Exactly -Times 1 -Scope It + } + + It 'Should return False when an IPv4 Subnet does not exist but one is configured' { + Mock -CommandName Get-DnsServerClientSubnet $mocks.Absent + $params = @{ + Ensure = 'Present' + Name = 'ClientSubnetA' + IPv4Subnet = '10.1.20.0/24' + } + Test-TargetResource @params | Should -BeFalse + + Assert-MockCalled -CommandName Get-DnsServerClientSubnet -Exactly -Times 1 -Scope It + } + + It 'Should return False when the IPv4 Subnet does not match what is configured' { + Mock -CommandName Get-DnsServerClientSubnet $mocks.GetIPv4Present + $params = @{ + Ensure = 'Present' + Name = 'ClientSubnetA' + IPv4Subnet = '10.1.20.0/24' + } + Test-TargetResource @params | Should -BeFalse + + Assert-MockCalled -CommandName Get-DnsServerClientSubnet -Exactly -Times 1 -Scope It + } + + It 'Should return False when the IPv6 Subnet does not match what is configured' { + Mock -CommandName Get-DnsServerClientSubnet $mocks.GetIPv6Present + $params = @{ + Ensure = 'Present' + Name = 'ClientSubnetB' + IPv6Subnet = 'aab8::1/28' + } + Test-TargetResource @params | Should -BeFalse + + Assert-MockCalled -CommandName Get-DnsServerClientSubnet -Exactly -Times 1 -Scope It + } + + It 'Should return False when an IPv6 Subnet does not exist but one is configured' { + Mock -CommandName Get-DnsServerClientSubnet $mocks.Absent + $params = @{ + Ensure = 'Present' + Name = 'ClientSubnetB' + IPv6Subnet = 'db8::1/28' + } + Test-TargetResource @params | Should -BeFalse + + Assert-MockCalled -CommandName Get-DnsServerClientSubnet -Exactly -Times 1 -Scope It + } + } + } + #endregion + + #region Function Set-TargetResource + Describe "MSFT_xDnsServerClientSubnet\Set-TargetResource" -Tag 'Set' { + Context 'When configuring DNS Server Client Subnets' { + It 'Calls Add-DnsServerClientSubnet in the set method when the subnet does not exist' { + Mock -CommandName Get-DnsServerClientSubnet + Mock -CommandName Add-DnsServerClientSubnet + + $params = @{ + Ensure = 'Present' + Name = 'ClientSubnetA' + IPv4Subnet = '10.1.20.0/24' + } + Set-TargetResource @params + + Assert-MockCalled Add-DnsServerClientSubnet -Scope It -ParameterFilter { + $Name -eq 'ClientSubnetA' -and $IPv4Subnet -eq '10.1.20.0/24' + } + } + + It 'Calls Remove-DnsServerClientSubnet in the set method when Ensure is Absent' { + Mock -CommandName Remove-DnsServerClientSubnet + Mock -CommandName Get-DnsServerClientSubnet { return $mocks.IPv4Present } + $params = @{ + Ensure = 'Absent' + Name = 'ClientSubnetA' + IPv4Subnet = '10.1.20.0/24' + } + Set-TargetResource @params + + Assert-MockCalled Remove-DnsServerClientSubnet -Scope It + } + + It "Calls Set-DnsServerClientSubnet in the set method when Ensure is Present subnet is found" { + + Mock -CommandName Set-DnsServerClientSubnet + Mock -CommandName Get-DnsServerClientSubnet $mocks.IPv4Present + $params = @{ + Ensure = 'Present' + Name = 'ClientSubnetX' + IPv4Subnet = '10.1.1.0/24' + } + Set-TargetResource @params + + Assert-MockCalled Set-DnsServerClientSubnet -Scope It + } + + } + } + #endregion + } #end InModuleScope +} +finally +{ + #region FOOTER + Restore-TestEnvironment -TestEnvironment $TestEnvironment + #endregion +} diff --git a/appveyor.yml b/appveyor.yml index 437dab42..a21ba458 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,6 +7,9 @@ environment: gallery_api: secure: 9ekJzfsPCDBkyLrfmov83XbbhZ6E2N3z+B/Io8NbDetbHc6hWS19zsDmy7t0Vvxv +# The image that will be used when building the job matrix (DNS policies require Win Server 2016 onwards) +image: Visual Studio 2017 + install: - git clone https://github.com/PowerShell/DscResource.Tests - ps: Write-Verbose -Message "PowerShell version $($PSVersionTable.PSVersion)" -Verbose