From 556c8fcf16e660be78d0fe3a5557665af176093f Mon Sep 17 00:00:00 2001 From: Simon Heather Date: Sat, 8 Feb 2020 21:14:09 +0000 Subject: [PATCH 01/13] Refactor ADDomain Resource and Tests --- Tests/Unit/MSFT_ADDomain.Tests.ps1 | 868 +++++++----------- .../MSFT_ADDomain/MSFT_ADDomain.psm1 | 400 +++----- .../en-US/MSFT_ADDomain.strings.psd1 | 34 +- 3 files changed, 491 insertions(+), 811 deletions(-) diff --git a/Tests/Unit/MSFT_ADDomain.Tests.ps1 b/Tests/Unit/MSFT_ADDomain.Tests.ps1 index d95a8118b..1b485e092 100644 --- a/Tests/Unit/MSFT_ADDomain.Tests.ps1 +++ b/Tests/Unit/MSFT_ADDomain.Tests.ps1 @@ -31,683 +31,467 @@ Invoke-TestSetup try { InModuleScope $script:dscResourceName { + Set-StrictMode -Version 1.0 + # Load stub cmdlets and classes. Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs\ActiveDirectory_2019.psm1') -Force Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs\ADDSDeployment_2019.psm1') -Force $mockDomainName = 'contoso.com' + $mockForestName = 'contoso.com' + $mockDnsRootProperty = 'contoso.com' $mockNetBiosName = 'CONTOSO' $mockDnsRoot = $mockDomainName - $forestMode = [Microsoft.DirectoryServices.Deployment.Types.ForestMode]::Win2012R2 - $mgmtForestMode = [Microsoft.ActiveDirectory.Management.ADForestMode]::Windows2012R2Forest - $domainMode = [Microsoft.DirectoryServices.Deployment.Types.DomainMode]::Win2012R2 - $mgmtDomainMode = [Microsoft.ActiveDirectory.Management.ADDomainMode]::Windows2012R2Domain - - $mockAdministratorCredential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList @( - 'DummyUser', - (ConvertTo-SecureString -String 'DummyPassword' -AsPlainText -Force) - ) + $mockParentDomainName = '' + $mockDomainFQDN = $mockDomainName + $mockNTDSPath = 'C:\Windows\NTDS' + $mockSysVolPath = 'C:\Windows\SYSVOL\sysvol' + $mockDomainSysVolPath = Join-Path -Path $mockSysVolPath -ChildPath $mockDomainName + $maxRetries = 15 + $forestMode = [Microsoft.DirectoryServices.Deployment.Types.ForestMode]::WinThreshold + $mgmtForestMode = [Microsoft.ActiveDirectory.Management.ADForestMode]::Windows2016Forest + $domainMode = [Microsoft.DirectoryServices.Deployment.Types.DomainMode]::WinThreshold + $mgmtDomainMode = [Microsoft.ActiveDirectory.Management.ADDomainMode]::Windows2016Domain + $NTDSParametersRegPath = 'HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Parameters' + $NetlogonParametersRegPath = 'HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' + + $mockAdministratorCredential = [System.Management.Automation.PSCredential]::new('DummyUser', + (ConvertTo-SecureString -String 'DummyPassword' -AsPlainText -Force)) + $mockSafemodePassword = (ConvertTo-SecureString -String 'DummyPassword' -AsPlainText -Force) + $mockSafemodeCredential = [System.Management.Automation.PSCredential]::new('Safemode', $mockSafemodePassword) + + $mockADDomainAbsent = @{ + DomainName = $mockDomainName + Credential = $mockAdministratorCredential + SafeModeAdministratorPassword = $mockSafemodeCredential + ParentDomainName = $mockParentDomainName + DomainNetBiosName = $null + DnsDelegationCredential = $null + DatabasePath = $null + LogPath = $null + SysvolPath = $null + ForestMode = $null + DomainMode = $null + DomainExist = $false + Forest = $null + DnsRoot = $null + } - $mockDefaultParameters = @{ + $mockADDomainPresent = @{ DomainName = $mockDomainName Credential = $mockAdministratorCredential - SafeModeAdministratorPassword = $mockAdministratorCredential + SafeModeAdministratorPassword = $mockSafemodeCredential + ParentDomainName = $mockParentDomainName + DomainNetBiosName = $mockNetBiosName + DnsDelegationCredential = $null + DatabasePath = $mockNTDSPath + LogPath = $mockNTDSPath + SysvolPath = $mockSysVolPath + ForestMode = $ForestMode + DomainMode = $DomainMode + DomainExist = $true + Forest = $mockForestName + DnsRoot = $mockDnsRootProperty } - #region Function Get-TargetResource Describe 'ADDomain\Get-TargetResource' { - BeforeEach { - Mock -CommandName Assert-Module - } - - Context 'When there is an authentication error' { - BeforeAll { - Mock -CommandName Get-ADDomain -MockWith { - throw New-Object -TypeName 'System.Security.Authentication.AuthenticationException' - } - - Mock -CommandName Test-Path -MockWith { - return $false - } - - Mock -CommandName Test-DomainMember -MockWith { - return $false - } - - $getTargetResourceParameters = $mockDefaultParameters.Clone() + BeforeAll { + $mockGetTargetResourceParameters = @{ + DomainName = $mockDomainName + Credential = $mockAdministratorCredential + SafeModeAdministratorPassword = $mockSafemodeCredential } - It 'Should throw the correct error' { - { Get-TargetResource @getTargetResourceParameters } | Should -Throw ($script:localizedData.InvalidCredentialError -f $mockDomainName) - } - } - - Context 'When an exception other than the known is thrown' { + Mock -CommandName Assert-Module + Mock -CommandName Test-Path ` + -ParameterFilter { $Path -eq $mockDomainSysVolPath } ` + -MockWith { $true } + Mock -CommandName Get-AdDomain ` + -MockWith { $mockGetADDomainResult } + Mock -CommandName Get-AdForest ` + -MockWith { $mockGetADForestResult } + Mock -CommandName Get-ItemProperty ` + -ParameterFilter { $Path -eq $NTDSParametersRegPath } ` + -MockWith { $mockGetItemPropertyNTDSResult } + Mock -CommandName Get-ItemProperty ` + -ParameterFilter { $Path -eq $NetlogonParametersRegPath } ` + -MockWith { $mockGetItemPropertyNetlogonResult } + } + + Context 'When the domain has not yet been installed' { BeforeAll { - Mock -CommandName Get-ADDomain -MockWith { - throw New-Object -TypeName 'System.Management.Automation.RunTimeException' -ArgumentList @('mocked error') - } + Mock -CommandName Get-ItemPropertyValue ` + -ParameterFilter { + $Path -eq $NetlogonParametersRegPath -and $Name -eq 'SysVol' } ` + -MockWith { throw [System.Management.Automation.ProviderInvocationException] } - Mock -CommandName Test-Path -MockWith { - return $false - } + $result = Get-TargetResource @mockGetTargetResourceParameters + } - Mock -CommandName Test-DomainMember -MockWith { - return $true + foreach ($property in $mockADDomainAbsent.Keys) + { + It "Should return the correct $property property" { + $result.$property | Should -Be $mockADDomainAbsent.$property } - - $getTargetResourceParameters = $mockDefaultParameters.Clone() } - It 'Should throw the correct error' { - { Get-TargetResource @getTargetResourceParameters } | Should -Throw 'mocked error' + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Assert-Module ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Get-ItemPropertyValue ` + -ParameterFilter { $Path -eq $NetlogonParametersRegPath ` + -and $Name -eq 'SysVol' } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Test-Path ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Get-ADDomain ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Get-ADForest ` + -Exactly -Times 0 + Assert-MockCalled -CommandName Get-ItemProperty ` + -Exactly -Times 0 } } - Context 'When the node is already a domain member and cannot be provisioned as a domain controller for another domain' { + Context 'When the domain has been installed' { BeforeAll { - Mock -CommandName Get-ADDomain -MockWith { - throw New-Object -TypeName 'Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException' + $mockGetADDomainResult = @{ + Forest = $mockForestName + DomainMode = $mgmtDomainMode + ParentDomain = $mockParentDomainName + NetBIOSName = $mockNetBiosName + DnsRoot = $mockDnsRoot } - Mock -CommandName Test-Path -MockWith { - return $false + $mockGetADForestResult = @{ + Name = $mockForestName + ForestMode = $mgmtForestMode } - Mock -CommandName Test-DomainMember -MockWith { - return $true + $mockGetItemPropertyNTDSResult = @{ + 'DSA Working Directory' = $mockNTDSPath + 'Database log files path' = $mockNTDSPath } - $getTargetResourceParameters = $mockDefaultParameters.Clone() - } - - It 'Should throw the correct error' { - { Get-TargetResource @getTargetResourceParameters } | Should -Throw ($script:localizedData.ExistingDomainMemberError -f $mockDomainName) - } - } - - Context 'When the system is in the desired state' { - BeforeAll { - Mock -CommandName Get-ADDomain -MockWith { - [PSObject] @{ - Forest = $mockDomainName - DomainMode = $mgmtDomainMode - ParentDomain = $mockDomainName - NetBIOSName = $mockNetBiosName - DnsRoot = $mockDnsRoot - } + $mockGetItemPropertyNetlogonResult = @{ + SysVol = $mockSysVolPath } - Mock -CommandName Get-ADForest -MockWith { - [PSObject] @{ - Name = $mockDomainName - ForestMode = $mgmtForestMode - } - } - } + Mock -CommandName Get-ItemPropertyValue ` + -ParameterFilter { $Path -eq $NetlogonParametersRegPath ` + -and $Name -eq 'SysVol' } ` + -MockWith { $mockSysVolPath } - Context 'When the domain exists' { - Context 'When the node is a domain member' { - BeforeAll { - Mock -CommandName Test-Path -MockWith { - return $true - } - - Mock -CommandName Test-DomainMember -MockWith { - return $true - } - - $getTargetResourceParameters = $mockDefaultParameters.Clone() - } - - It 'Should call the correct mocks' { - $null = Get-TargetResource @getTargetResourceParameters - - Assert-MockCalled -CommandName Test-DomainMember -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ADDomain -ParameterFilter { - -not $PSBoundParameters.ContainsKey('Credential') - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-ADForest -ParameterFilter { - -not $PSBoundParameters.ContainsKey('Credential') - } -Exactly -Times 1 -Scope It - } - - It 'Should return the same values as passed as parameters' { - $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters - $getTargetResourceResult.DomainName | Should -Be $getTargetResourceParameters.DomainName - $getTargetResourceResult.Credential.UserName | Should -Be $getTargetResourceParameters.Credential.UserName - $getTargetResourceResult.SafeModeAdministratorPassword.UserName | Should -Be $getTargetResourceParameters.SafeModeAdministratorPassword.UserName - } - - It 'Should return correct values for the rest of the properties' { - $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters - $getTargetResourceResult.DnsRoot | Should -Be $mockDomainName - $getTargetResourceResult.ParentDomainName | Should -Be $mockDomainName - $getTargetResourceResult.DomainNetBiosName | Should -Be $mockNetBiosName - $getTargetResourceResult.DnsDelegationCredential | Should -BeNullOrEmpty - $getTargetResourceResult.DatabasePath | Should -BeNullOrEmpty - $getTargetResourceResult.LogPath | Should -BeNullOrEmpty - $getTargetResourceResult.SysvolPath | Should -BeNullOrEmpty - $getTargetResourceResult.ForestMode | Should -Be 'Win2012R2' - $getTargetResourceResult.DomainMode | Should -Be 'Win2012R2' - $getTargetResourceResult.DomainExist | Should -BeTrue - } - } - - Context 'When no tracking file was found' { - BeforeAll { - Mock -CommandName Write-Warning - Mock -CommandName Test-Path -MockWith { - return $false - } - - Mock -CommandName Test-DomainMember -MockWith { - return $true - } - - $getTargetResourceParameters = $mockDefaultParameters.Clone() - } - - It 'Should call the correct mocks' { - $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters - - Assert-MockCalled -CommandName Write-Warning -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Test-DomainMember -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ADDomain -ParameterFilter { - -not $PSBoundParameters.ContainsKey('Credential') - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-ADForest -ParameterFilter { - -not $PSBoundParameters.ContainsKey('Credential') - } -Exactly -Times 1 -Scope It - } - } + $result = Get-TargetResource @mockGetTargetResourceParameters + } - Context 'When the node is not a domain member' { - BeforeAll { - Mock -CommandName Test-Path -MockWith { - return $true - } - - Mock -CommandName Test-DomainMember -MockWith { - return $false - } - - $getTargetResourceParameters = $mockDefaultParameters.Clone() - } - - It 'Should call the correct mocks' { - $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters - - Assert-MockCalled -CommandName Test-DomainMember -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ADDomain -ParameterFilter { - $PSBoundParameters.ContainsKey('Credential') - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-ADForest -ParameterFilter { - $PSBoundParameters.ContainsKey('Credential') - } -Exactly -Times 1 -Scope It - } - - It 'Should return the same values as passed as parameters' { - $result = Get-TargetResource @getTargetResourceParameters - $result.DomainName | Should -Be $getTargetResourceParameters.DomainName - $result.Credential.UserName | Should -Be $getTargetResourceParameters.Credential.UserName - $result.SafeModeAdministratorPassword.UserName | Should -Be $getTargetResourceParameters.SafeModeAdministratorPassword.UserName - } - - It 'Should return correct values for the rest of the properties' { - $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters - $getTargetResourceResult.DnsRoot | Should -Be $mockDomainName - $getTargetResourceResult.ParentDomainName | Should -Be $mockDomainName - $getTargetResourceResult.DomainNetBiosName | Should -Be $mockNetBiosName - $getTargetResourceResult.DnsDelegationCredential | Should -BeNullOrEmpty - $getTargetResourceResult.DatabasePath | Should -BeNullOrEmpty - $getTargetResourceResult.LogPath | Should -BeNullOrEmpty - $getTargetResourceResult.SysvolPath | Should -BeNullOrEmpty - $getTargetResourceResult.ForestMode | Should -Be 'Win2012R2' - $getTargetResourceResult.DomainMode | Should -Be 'Win2012R2' - $getTargetResourceResult.DomainExist | Should -BeTrue - } + foreach ($property in $mockADDomainPresent.Keys) + { + It "Should return the correct $property property" { + $result.$property | Should -Be $mockADDomainPresent.$property } } - } - Context 'When the system is not in the desired state' { - BeforeAll { - Mock -CommandName Get-ADForest - Mock -CommandName Get-ADDomain -MockWith { - throw New-Object -TypeName 'Microsoft.ActiveDirectory.Management.ADServerDownException' - } + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Assert-Module ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Get-ItemPropertyValue ` + -ParameterFilter { $Path -eq $NetlogonParametersRegPath -and $Name -eq 'SysVol' } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Test-Path ` + -ParameterFilter { $Path -eq $mockDomainSysVolPath } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Get-ADDomain ` + -ParameterFilter { $Identity -eq $mockDomainFQDN } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Get-ADForest ` + -ParameterFilter { $Identity -eq $mockForestName } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Get-ItemProperty ` + -ParameterFilter { $Path -eq $NetlogonParametersRegPath } ` + -Exactly -Times 1 + Assert-MockCalled -CommandName Get-ItemProperty ` + -ParameterFilter { $Path -eq $NTDSParametersRegPath } ` + -Exactly -Times 1 } - Context 'When the domain does not exist' { + Context 'When the correct domain SysVol path does not exist' { BeforeAll { - Mock -CommandName Test-Path -MockWith { - return $false - } - - Mock -CommandName Test-DomainMember -MockWith { - return $false - } - - $getTargetResourceParameters = $mockDefaultParameters.Clone() - } - - It 'Should call the correct mocks' { - $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters - - Assert-MockCalled -CommandName Test-DomainMember -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ADForest -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-ADDomain -ParameterFilter { - $PSBoundParameters.ContainsKey('Credential') - } -Exactly -Times 1 -Scope It + Mock -CommandName Test-Path ` + -ParameterFilter { $Path -eq $mockDomainSysVolPath } ` + -MockWith { $false } } - It 'Should return the same values as passed as parameters' { - $result = Get-TargetResource @getTargetResourceParameters - $result.DomainName | Should -Be $getTargetResourceParameters.DomainName - $result.Credential.UserName | Should -Be $getTargetResourceParameters.Credential.UserName - $result.SafeModeAdministratorPassword.UserName | Should -Be $getTargetResourceParameters.SafeModeAdministratorPassword.UserName + It 'Should throw the correct exception' { + { Get-TargetResource @mockGetTargetResourceParameters } | + Should -Throw ($script:localizedData.SysVolPathDoesNotExistError -f $mockDomainSysVolPath) } + } - It 'Should return $false for the property DomainExist' { - $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters - $getTargetResourceResult.DomainExist | Should -BeFalse + Context 'When Get-ADDomain throws an unexpected error' { + BeforeAll { + Mock -CommandName Get-AdDomain ` + -MockWith { Throw 'Unknown Error' } } - It 'Should return $null for the rest of the properties' { - $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters - $getTargetResourceResult.DnsRoot | Should -BeNullOrEmpty - $getTargetResourceResult.ParentDomainName | Should -BeNullOrEmpty - $getTargetResourceResult.DomainNetBiosName | Should -BeNullOrEmpty - $getTargetResourceResult.DnsDelegationCredential | Should -BeNullOrEmpty - $getTargetResourceResult.DatabasePath | Should -BeNullOrEmpty - $getTargetResourceResult.LogPath | Should -BeNullOrEmpty - $getTargetResourceResult.SysvolPath | Should -BeNullOrEmpty - $getTargetResourceResult.ForestMode | Should -BeNullOrEmpty - $getTargetResourceResult.DomainMode | Should -BeNullOrEmpty + It 'Should throw the correct exception' { + { Get-TargetResource @mockGetTargetResourceParameters } | + Should -Throw ($script:localizedData.GetAdDomainUnexpectedError -f $mockDomainFQDN) } } - Context 'When the domain cannot be found, but should exist (tracking file exist)' { + Context 'When Get-ADDomain throws an ADServerDownException until timeout' { BeforeAll { + Mock -CommandName Get-AdDomain ` + -MockWith { throw New-Object -TypeName 'Microsoft.ActiveDirectory.Management.ADServerDownException' } Mock -CommandName Start-Sleep - Mock -CommandName Test-Path -MockWith { - return $true - } - - Mock -CommandName Test-DomainMember -MockWith { - return $true - } - - $getTargetResourceParameters = $mockDefaultParameters.Clone() } - It 'Should call the correct mocks the correct number of times' { - $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters - - Assert-MockCalled -CommandName Test-DomainMember -Exactly -Times 5 -Scope It - Assert-MockCalled -CommandName Get-ADForest -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Start-Sleep -Exactly -Times 5 -Scope It - Assert-MockCalled -CommandName Get-ADDomain -Exactly -Times 5 -Scope It + It 'Should throw the correct exception' { + { Get-TargetResource @mockGetTargetResourceParameters } | + Should -Throw ($script:localizedData.MaxDomainRetriesReachedError -f $mockDomainFQDN) } - It 'Should return the same values as passed as parameters' { - $result = Get-TargetResource @getTargetResourceParameters - $result.DomainName | Should -Be $getTargetResourceParameters.DomainName - $result.Credential.UserName | Should -Be $getTargetResourceParameters.Credential.UserName - $result.SafeModeAdministratorPassword.UserName | Should -Be $getTargetResourceParameters.SafeModeAdministratorPassword.UserName + It 'Should call the expected mocks' { + Assert-MockCalled -CommandName Get-ADDomain ` + -ParameterFilter { $Identity -eq $mockDomainFQDN } ` + -Exactly -Times $maxRetries + Assert-MockCalled -CommandName Start-Sleep ` + -Exactly -Times $maxRetries } + } - It 'Should return $false for the property DomainExist' { - $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters - $getTargetResourceResult.DomainExist | Should -BeFalse + Context 'When Get-ADForest throws an unexpected error' { + BeforeAll { + Mock -CommandName Get-AdForest ` + -MockWith { Throw 'Unknown Error' } } - It 'Should return $null for the rest of the properties' { - $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters - $getTargetResourceResult.DnsRoot | Should -BeNullOrEmpty - $getTargetResourceResult.ParentDomainName | Should -BeNullOrEmpty - $getTargetResourceResult.DomainNetBiosName | Should -BeNullOrEmpty - $getTargetResourceResult.DnsDelegationCredential | Should -BeNullOrEmpty - $getTargetResourceResult.DatabasePath | Should -BeNullOrEmpty - $getTargetResourceResult.LogPath | Should -BeNullOrEmpty - $getTargetResourceResult.SysvolPath | Should -BeNullOrEmpty - $getTargetResourceResult.ForestMode | Should -BeNullOrEmpty - $getTargetResourceResult.DomainMode | Should -BeNullOrEmpty + It 'Should throw the correct exception' { + { Get-TargetResource @mockGetTargetResourceParameters } | + Should -Throw ($script:localizedData.GetAdForestUnexpectedError -f $mockForestName) } } } } - #endregion - #region Function Test-TargetResource Describe 'ADDomain\Test-TargetResource' { - $mockDomainName = 'present.com' - $correctChildDomainName = 'present' - $correctDomainNetBIOSName = 'PRESENT' - $incorrectDomainName = 'incorrect.com' - $parentDomainName = 'parent.com' - $mockAdministratorCredential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList @( - 'DummyUser', - (ConvertTo-SecureString -String 'DummyPassword' -AsPlainText -Force) - ) - - $mockDefaultParameters = @{ + $mockTestTargetResourceParameters = @{ + DomainName = $mockDomainName Credential = $mockAdministratorCredential - SafeModeAdministratorPassword = $mockAdministratorCredential + SafeModeAdministratorPassword = $mockSafemodeCredential } - $stubDomain = @{ - DnsRoot = $mockDomainName - DomainNetBIOSName = $correctDomainNetBIOSName - } + It 'Returns "True" when the resource is in the desired state' { + Mock -CommandName Get-TargetResource -MockWith { return $mockADDomainPresent } - # Get-TargetResource returns the domain FQDN for .DomainName - $stubChildDomain = @{ - DnsRoot = "$correctChildDomainName.$parentDomainName" - ParentDomainName = $parentDomainName - DomainNetBIOSName = $correctDomainNetBIOSName + Test-TargetResource @mockTestTargetResourceParameters | Should -BeTrue } - It 'Returns "True" when "DomainName" matches' { - Mock -CommandName Get-TargetResource -MockWith { return $stubDomain } - - $result = Test-TargetResource @mockDefaultParameters -DomainName $mockDomainName - - $result | Should -BeTrue - } - - It 'Returns "False" when "DomainName" does not match' { - Mock -CommandName Get-TargetResource -MockWith { return $stubDomain } - - $result = Test-TargetResource @mockDefaultParameters -DomainName $incorrectDomainName + It 'Returns "False" when the resource is not in the desired state' { + Mock -CommandName Get-TargetResource -MockWith { return $mockADDomainAbsent } - $result | Should -BeFalse + Test-TargetResource @mockTestTargetResourceParameters | Should -BeFalse } - - It 'Returns "True" when "DomainNetBIOSName" matches' { - Mock -CommandName Get-TargetResource -MockWith { return $stubDomain } - - $result = Test-TargetResource @mockDefaultParameters -DomainName $mockDomainName -DomainNetBIOSName $correctDomainNetBIOSName - - $result | Should -BeTrue - } - - It 'Returns "False" when "DomainNetBIOSName" does not match' { - Mock -CommandName Get-TargetResource -MockWith { return $stubDomain } - - $result = Test-TargetResource @mockDefaultParameters -DomainName $mockDomainName -DomainNetBIOSName 'INCORRECT' - - $result | Should -BeFalse - } - - It 'Returns "True" when "ParentDomainName" matches' { - Mock -CommandName Get-TargetResource -MockWith { return $stubChildDomain } - - $result = Test-TargetResource @mockDefaultParameters -DomainName $correctChildDomainName -ParentDomainName $parentDomainName - - $result | Should -BeTrue - } - - It 'Returns "False" when "ParentDomainName" does not match' { - Mock -CommandName Get-TargetResource -MockWith { return $stubChildDomain } - - $result = Test-TargetResource @mockDefaultParameters -DomainName $correctChildDomainName -ParentDomainName 'incorrect.com' - - $result | Should -BeFalse - } - } - #endregion - #region Function Set-TargetResource Describe 'ADDomain\Set-TargetResource' { - $testDomainName = 'present.com' - $testParentDomainName = 'parent.com' - $testDomainNetBIOSNameName = 'PRESENT' - $testDomainForestMode = 'WinThreshold' - - $mockAdministratorCredential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList @( - 'Admin', - (ConvertTo-SecureString -String 'DummyPassword' -AsPlainText -Force) - ) - - $testSafemodePassword = (ConvertTo-SecureString -String 'DummyPassword' -AsPlainText -Force) - $testSafemodeCredential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList @( - 'Safemode', - $testSafemodePassword - ) - - $testDelegationCredential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList @( - 'Delegation', - (ConvertTo-SecureString -String 'DummyPassword' -AsPlainText -Force) - ) - - $newForestParams = @{ - DomainName = $testDomainName - Credential = $mockAdministratorCredential - SafeModeAdministratorPassword = $testSafemodeCredential - } - - $newDomainParams = @{ - DomainName = $testDomainName - ParentDomainName = $testParentDomainName - Credential = $mockAdministratorCredential - SafeModeAdministratorPassword = $testSafemodeCredential - } - - $stubTargetResource = @{ - DomainName = $testDomainName - ParentDomainName = $testParentDomainName - DomainNetBIOSName = $testDomainNetBIOSNameName - ForestName = $testParentDomainName - ForestMode = $testDomainForestMode - DomainMode = $testDomainForestMode - DomainExist = $false - } - - Mock -CommandName Get-TargetResource -MockWith { return $stubTargetResource } - Mock -CommandName Out-File - - It 'Calls "Install-ADDSForest" with "DomainName" when creating forest' { - Mock -CommandName Install-ADDSForest -ParameterFilter { $DomainName -eq $testDomainName } - - Set-TargetResource @newForestParams - - Assert-MockCalled -CommandName Install-ADDSForest -ParameterFilter { $DomainName -eq $testDomainName } -Scope It - } - - It 'Calls "Install-ADDSForest" with "SafeModeAdministratorPassword" when creating forest' { - Mock -CommandName Install-ADDSForest -ParameterFilter { $SafeModeAdministratorPassword -eq $testSafemodePassword } - - Set-TargetResource @newForestParams - - Assert-MockCalled -CommandName Install-ADDSForest -ParameterFilter { $SafeModeAdministratorPassword -eq $testSafemodePassword } -Scope It - } - - It 'Calls "Install-ADDSForest" with "DnsDelegationCredential" when creating forest, if specified' { - Mock -CommandName Install-ADDSForest -ParameterFilter { $DnsDelegationCredential -eq $testDelegationCredential } - - Set-TargetResource @newForestParams -DnsDelegationCredential $testDelegationCredential - - Assert-MockCalled -CommandName Install-ADDSForest -ParameterFilter { $DnsDelegationCredential -eq $testDelegationCredential } -Scope It - } - - It 'Calls "Install-ADDSForest" with "CreateDnsDelegation" when creating forest, if specified' { - Mock -CommandName Install-ADDSForest -ParameterFilter { $CreateDnsDelegation -eq $true } - - Set-TargetResource @newForestParams -DnsDelegationCredential $testDelegationCredential - - Assert-MockCalled -CommandName Install-ADDSForest -ParameterFilter { $CreateDnsDelegation -eq $true } -Scope It - } - - It 'Calls "Install-ADDSForest" with "DatabasePath" when creating forest, if specified' { - $testPath = 'TestPath' - Mock -CommandName Install-ADDSForest -ParameterFilter { $DatabasePath -eq $testPath } - - Set-TargetResource @newForestParams -DatabasePath $testPath - - Assert-MockCalled -CommandName Install-ADDSForest -ParameterFilter { $DatabasePath -eq $testPath } -Scope It - } - - It 'Calls "Install-ADDSForest" with "LogPath" when creating forest, if specified' { - $testPath = 'TestPath' - Mock -CommandName Install-ADDSForest -ParameterFilter { $LogPath -eq $testPath } + $mockDomainName = 'present.com' + $mockParentDomainName = 'parent.com' + $mockDomainNetBIOSNameName = 'PRESENT' + $mockDomainForestMode = 'WinThreshold' + $mockPath = 'TestPath' - Set-TargetResource @newForestParams -LogPath $testPath + $mockDelegationCredential = [System.Management.Automation.PSCredential]::new('Delegation', + (ConvertTo-SecureString -String 'DummyPassword' -AsPlainText -Force)) - Assert-MockCalled -CommandName Install-ADDSForest -ParameterFilter { $LogPath -eq $testPath } -Scope It + $setTargetResourceForestParams = @{ + DomainName = $mockDomainName + Credential = $mockAdministratorCredential + SafeModeAdministratorPassword = $mockSafemodeCredential } - It 'Calls "Install-ADDSForest" with "SysvolPath" when creating forest, if specified' { - $testPath = 'TestPath' - Mock -CommandName Install-ADDSForest -ParameterFilter { $SysvolPath -eq $testPath } - - Set-TargetResource @newForestParams -SysvolPath $testPath - - Assert-MockCalled -CommandName Install-ADDSForest -ParameterFilter { $SysvolPath -eq $testPath } -Scope It + $setTargetResourceDomainParams = @{ + DomainName = $mockDomainName + ParentDomainName = $mockParentDomainName + Credential = $mockAdministratorCredential + SafeModeAdministratorPassword = $mockSafemodeCredential } - It 'Calls "Install-ADDSForest" with "DomainNetbiosName" when creating forest, if specified' { - Mock -CommandName Install-ADDSForest -ParameterFilter { $DomainNetbiosName -eq $testDomainNetBIOSNameName } + Mock -CommandName Get-TargetResource -MockWith { return $mockADDomainAbsent } - Set-TargetResource @newForestParams -DomainNetBIOSName $testDomainNetBIOSNameName + Context 'When Installing a Forest Root Domain' { + BeforeAll { + Mock -CommandName Install-ADDSForest + } - Assert-MockCalled -CommandName Install-ADDSForest -ParameterFilter { $DomainNetbiosName -eq $testDomainNetBIOSNameName } -Scope It - } + It 'Calls "Install-ADDSForest" with "DomainName"' { + Set-TargetResource @setTargetResourceForestParams - It 'Calls "Install-ADDSForest" with "ForestMode" when creating forest, if specified' { - Mock -CommandName Install-ADDSForest -ParameterFilter { $ForestMode -eq $testDomainForestMode } + Assert-MockCalled -CommandName Install-ADDSForest ` + -ParameterFilter { $DomainName -eq $mockDomainName } + } - Set-TargetResource @newForestParams -ForestMode $testDomainForestMode + It 'Calls "Install-ADDSForest" with "SafeModeAdministratorPassword"' { + Set-TargetResource @setTargetResourceForestParams - Assert-MockCalled -CommandName Install-ADDSForest -ParameterFilter { $ForestMode -eq $testDomainForestMode } -Scope It - } + Assert-MockCalled -CommandName Install-ADDSForest ` + -ParameterFilter { $SafeModeAdministratorPassword -eq $mockSafemodePassword } + } - It 'Calls "Install-ADDSForest" with "DomainMode" when creating forest, if specified' { - Mock -CommandName Install-ADDSForest -ParameterFilter { $DomainMode -eq $testDomainForestMode } + It 'Calls "Install-ADDSForest" with "DnsDelegationCredential", if specified' { + Set-TargetResource @setTargetResourceForestParams ` + -DnsDelegationCredential $mockDelegationCredential - Set-TargetResource @newForestParams -DomainMode $testDomainForestMode + Assert-MockCalled -CommandName Install-ADDSForest ` + -ParameterFilter { $DnsDelegationCredential -eq $mockDelegationCredential } + } - Assert-MockCalled -CommandName Install-ADDSForest -ParameterFilter { $DomainMode -eq $testDomainForestMode } -Scope It - } + It 'Calls "Install-ADDSForest" with "CreateDnsDelegation", if specified' { + Set-TargetResource @setTargetResourceForestParams ` + -DnsDelegationCredential $mockDelegationCredential - # ADDSDomain + Assert-MockCalled -CommandName Install-ADDSForest ` + -ParameterFilter { $CreateDnsDelegation -eq $true } + } - It 'Calls "Install-ADDSDomain" with "NewDomainName" when creating child domain' { - Mock -CommandName Install-ADDSDomain -ParameterFilter { $NewDomainName -eq $testDomainName } + It 'Calls "Install-ADDSForest" with "DatabasePath", if specified' { + Set-TargetResource @setTargetResourceForestParams -DatabasePath $mockPath - Set-TargetResource @newDomainParams + Assert-MockCalled -CommandName Install-ADDSForest -ParameterFilter { $DatabasePath -eq $mockPath } + } - Assert-MockCalled -CommandName Install-ADDSDomain -ParameterFilter { $NewDomainName -eq $testDomainName } -Scope It - } + It 'Calls "Install-ADDSForest" with "LogPath", if specified' { + Set-TargetResource @setTargetResourceForestParams -LogPath $mockPath - It 'Calls "Install-ADDSDomain" with "ParentDomainName" when creating child domain' { - Mock -CommandName Install-ADDSDomain -ParameterFilter { $ParentDomainName -eq $testParentDomainName } + Assert-MockCalled -CommandName Install-ADDSForest -ParameterFilter { $LogPath -eq $mockPath } + } - Set-TargetResource @newDomainParams + It 'Calls "Install-ADDSForest" with "SysvolPath", if specified' { + Set-TargetResource @setTargetResourceForestParams -SysvolPath $mockPath - Assert-MockCalled -CommandName Install-ADDSDomain -ParameterFilter { $ParentDomainName -eq $testParentDomainName } -Scope It - } + Assert-MockCalled -CommandName Install-ADDSForest -ParameterFilter { $SysvolPath -eq $mockPath } + } - It 'Calls "Install-ADDSDomain" with "DomainType" when creating child domain' { - Mock -CommandName Install-ADDSDomain -ParameterFilter { $DomainType -eq 'ChildDomain' } + It 'Calls "Install-ADDSForest" with "DomainNetbiosName", if specified' { + Set-TargetResource @setTargetResourceForestParams -DomainNetBIOSName $mockDomainNetBIOSNameName - Set-TargetResource @newDomainParams + Assert-MockCalled -CommandName Install-ADDSForest ` + -ParameterFilter { $DomainNetbiosName -eq $mockDomainNetBIOSNameName } + } - Assert-MockCalled -CommandName Install-ADDSDomain -ParameterFilter { $DomainType -eq 'ChildDomain' } -Scope It - } + It 'Calls "Install-ADDSForest" with "ForestMode", if specified' { + Set-TargetResource @setTargetResourceForestParams -ForestMode $mockDomainForestMode - It 'Calls "Install-ADDSDomain" with "SafeModeAdministratorPassword" when creating child domain' { - Mock -CommandName Install-ADDSDomain -ParameterFilter { $SafeModeAdministratorPassword -eq $testSafemodePassword } + Assert-MockCalled -CommandName Install-ADDSForest ` + -ParameterFilter { $ForestMode -eq $mockDomainForestMode } + } - Set-TargetResource @newDomainParams + It 'Calls "Install-ADDSForest" with "DomainMode", if specified' { + Set-TargetResource @setTargetResourceForestParams -DomainMode $mockDomainForestMode - Assert-MockCalled -CommandName Install-ADDSDomain -ParameterFilter { $SafeModeAdministratorPassword -eq $testSafemodePassword } -Scope It + Assert-MockCalled -CommandName Install-ADDSForest ` + -ParameterFilter { $DomainMode -eq $mockDomainForestMode } + } } - It 'Calls "Install-ADDSDomain" with "Credential" when creating child domain' { - Mock -CommandName Install-ADDSDomain -ParameterFilter { $Credential -eq $testParentDomainName } + Context 'When Installing a Child Domain' { + BeforeAll { + Mock -CommandName Install-ADDSDomain + } - Set-TargetResource @newDomainParams + It 'Calls "Install-ADDSDomain" with "NewDomainName"' { + Set-TargetResource @setTargetResourceDomainParams - Assert-MockCalled -CommandName Install-ADDSDomain -ParameterFilter { $ParentDomainName -eq $testParentDomainName } -Scope It - } + Assert-MockCalled -CommandName Install-ADDSDomain ` + -ParameterFilter { $NewDomainName -eq $mockDomainName } + } - It 'Calls "Install-ADDSDomain" with "ParentDomainName" when creating child domain' { - Mock -CommandName Install-ADDSDomain -ParameterFilter { $ParentDomainName -eq $testParentDomainName } + It 'Calls "Install-ADDSDomain" with "ParentDomainName"' { + Set-TargetResource @setTargetResourceDomainParams - Set-TargetResource @newDomainParams + Assert-MockCalled -CommandName Install-ADDSDomain ` + -ParameterFilter { $ParentDomainName -eq $mockParentDomainName } + } - Assert-MockCalled -CommandName Install-ADDSDomain -ParameterFilter { $ParentDomainName -eq $testParentDomainName } -Scope It - } + It 'Calls "Install-ADDSDomain" with "DomainType"' { + Set-TargetResource @setTargetResourceDomainParams - It 'Calls "Install-ADDSDomain" with "DnsDelegationCredential" when creating child domain, if specified' { - Mock -CommandName Install-ADDSDomain -ParameterFilter { $DnsDelegationCredential -eq $testDelegationCredential } + Assert-MockCalled -CommandName Install-ADDSDomain ` + -ParameterFilter { $DomainType -eq 'ChildDomain' } + } - Set-TargetResource @newDomainParams -DnsDelegationCredential $testDelegationCredential + It 'Calls "Install-ADDSDomain" with "SafeModeAdministratorPassword"' { + Set-TargetResource @setTargetResourceDomainParams - Assert-MockCalled -CommandName Install-ADDSDomain -ParameterFilter { $DnsDelegationCredential -eq $testDelegationCredential } -Scope It - } + Assert-MockCalled -CommandName Install-ADDSDomain ` + -ParameterFilter { $SafeModeAdministratorPassword -eq $mockSafemodePassword } + } - It 'Calls "Install-ADDSDomain" with "CreateDnsDelegation" when creating child domain, if specified' { - Mock -CommandName Install-ADDSDomain -ParameterFilter { $CreateDnsDelegation -eq $true } + It 'Calls "Install-ADDSDomain" with "Credential"' { + Set-TargetResource @setTargetResourceDomainParams - Set-TargetResource @newDomainParams -DnsDelegationCredential $testDelegationCredential + Assert-MockCalled -CommandName Install-ADDSDomain ` + -ParameterFilter { $ParentDomainName -eq $mockParentDomainName } + } - Assert-MockCalled -CommandName Install-ADDSDomain -ParameterFilter { $CreateDnsDelegation -eq $true } -Scope It - } + It 'Calls "Install-ADDSDomain" with "ParentDomainName"' { + Set-TargetResource @setTargetResourceDomainParams - It 'Calls "Install-ADDSDomain" with "DatabasePath" when creating child domain, if specified' { - $testPath = 'TestPath' - Mock -CommandName Install-ADDSDomain -ParameterFilter { $DatabasePath -eq $testPath } + Assert-MockCalled -CommandName Install-ADDSDomain ` + -ParameterFilter { $ParentDomainName -eq $mockParentDomainName } + } - Set-TargetResource @newDomainParams -DatabasePath $testPath + It 'Calls "Install-ADDSDomain" with "DnsDelegationCredential", if specified' { + Set-TargetResource @setTargetResourceDomainParams ` + -DnsDelegationCredential $mockDelegationCredential - Assert-MockCalled -CommandName Install-ADDSDomain -ParameterFilter { $DatabasePath -eq $testPath } -Scope It - } + Assert-MockCalled -CommandName Install-ADDSDomain ` + -ParameterFilter { $DnsDelegationCredential -eq $mockDelegationCredential } + } - It 'Calls "Install-ADDSDomain" with "LogPath" when creating child domain, if specified' { - $testPath = 'TestPath' - Mock -CommandName Install-ADDSDomain -ParameterFilter { $LogPath -eq $testPath } + It 'Calls "Install-ADDSDomain" with "CreateDnsDelegation", if specified' { + Set-TargetResource @setTargetResourceDomainParams ` + -DnsDelegationCredential $mockDelegationCredential - Set-TargetResource @newDomainParams -LogPath $testPath + Assert-MockCalled -CommandName Install-ADDSDomain ` + -ParameterFilter { $CreateDnsDelegation -eq $true } + } - Assert-MockCalled -CommandName Install-ADDSDomain -ParameterFilter { $LogPath -eq $testPath } -Scope It - } + It 'Calls "Install-ADDSDomain" with "DatabasePath", if specified' { + Set-TargetResource @setTargetResourceDomainParams -DatabasePath $mockPath - It 'Calls "Install-ADDSDomain" with "SysvolPath" when creating child domain, if specified' { - $testPath = 'TestPath' - Mock -CommandName Install-ADDSDomain -ParameterFilter { $SysvolPath -eq $testPath } + Assert-MockCalled -CommandName Install-ADDSDomain -ParameterFilter { $DatabasePath -eq $mockPath } + } - Set-TargetResource @newDomainParams -SysvolPath $testPath + It 'Calls "Install-ADDSDomain" with "LogPath", if specified' { + Set-TargetResource @setTargetResourceDomainParams -LogPath $mockPath - Assert-MockCalled -CommandName Install-ADDSDomain -ParameterFilter { $SysvolPath -eq $testPath } -Scope It - } + Assert-MockCalled -CommandName Install-ADDSDomain -ParameterFilter { $LogPath -eq $mockPath } + } - It 'Calls "Install-ADDSDomain" with "NewDomainNetbiosName" when creating child domain, if specified' { - Mock -CommandName Install-ADDSDomain -ParameterFilter { $NewDomainNetbiosName -eq $testDomainNetBIOSNameName } + It 'Calls "Install-ADDSDomain" with "SysvolPath", if specified' { + Set-TargetResource @setTargetResourceDomainParams -SysvolPath $mockPath - Set-TargetResource @newDomainParams -DomainNetBIOSName $testDomainNetBIOSNameName + Assert-MockCalled -CommandName Install-ADDSDomain -ParameterFilter { $SysvolPath -eq $mockPath } + } - Assert-MockCalled -CommandName Install-ADDSDomain -ParameterFilter { $NewDomainNetbiosName -eq $testDomainNetBIOSNameName } -Scope It - } + It 'Calls "Install-ADDSDomain" with "NewDomainNetbiosName", if specified' { + Set-TargetResource @setTargetResourceDomainParams -DomainNetBIOSName $mockDomainNetBIOSNameName - It 'Calls "Install-ADDSDomain" with "DomainMode" when creating child domain, if specified' { - Mock -CommandName Install-ADDSDomain -ParameterFilter { $DomainMode -eq $testDomainForestMode } + Assert-MockCalled -CommandName Install-ADDSDomain ` + -ParameterFilter { $NewDomainNetbiosName -eq $mockDomainNetBIOSNameName } + } - Set-TargetResource @newDomainParams -DomainMode $testDomainForestMode + It 'Calls "Install-ADDSDomain" with "DomainMode", if specified' { + Set-TargetResource @setTargetResourceDomainParams -DomainMode $mockDomainForestMode - Assert-MockCalled -CommandName Install-ADDSDomain -ParameterFilter { $DomainMode -eq $testDomainForestMode } -Scope It + Assert-MockCalled -CommandName Install-ADDSDomain ` + -ParameterFilter { $DomainMode -eq $mockDomainForestMode } + } } } - #endregion - } - #endregion } finally { diff --git a/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.psm1 b/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.psm1 index 922512585..bdd9a1431 100644 --- a/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.psm1 +++ b/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.psm1 @@ -6,46 +6,6 @@ Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath ' $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_ADDomain' -<# - .SYNOPSIS - Retrieves the name of the file that tracks the status of the ADDomain resource with the - specified domain name. - - .PARAMETER DomainName - The domain name of the ADDomain resource to retrieve the tracking file name of. - - .NOTES - The tracking file is currently output to the environment's temp directory. - - This file is NOT removed when a configuration completes, so if another call to a ADDomain - resource with the same domain name occurs in the same environment, this file will already - be present. - - This is so that when another call is made to the same resource, the resource will not - attempt to promote the machine to a domain controller again (which would cause an error). - - If the resource should be promoted to a domain controller once again, you must first remove - this file from the environment's temp directory (usually C:\Temp). - - If in the future this functionality needs to change so that future configurations are not - affected, $env:temp should be changed to the resource's cache location which is removed - after each configuration. - ($env:systemRoot\system32\Configuration\BuiltinProvCache\MSFT_ADDomain) -#> -function Get-TrackingFilename -{ - [OutputType([System.String])] - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $DomainName - ) - - return Join-Path -Path ($env:TEMP) -ChildPath ('{0}.ADDomain.completed' -f $DomainName) -} - <# .SYNOPSIS Get the current state of the Domain. @@ -68,32 +28,17 @@ function Get-TrackingFilename .PARAMETER ParentDomainName Fully qualified domain name (FQDN) of the parent domain. - .PARAMETER DomainNetBiosName - NetBIOS name for the new domain. - - .PARAMETER DnsDelegationCredential - Credential used for creating DNS delegation. - - .PARAMETER DatabasePath - Path to a directory that contains the domain database. - - .PARAMETER LogPath - Path to a directory for the log file that will be written. - - .PARAMETER SysvolPath - Path to a directory where the Sysvol file will be written. - - .PARAMETER ForestMode - The Forest Functional Level for the entire forest. - - .PARAMETER DomainMode - The Domain Functional Level for the entire domain. - .NOTES - No need to check whether the node is actually a domain controller. - Domain controller functionality should be checked by the - ADDomainController resource. - + Used Functions: + Name | Module + -------------------------------|-------------------------- + Get-ADDomain | ActiveDirectory + Get-ADForest | ActiveDirectory + Assert-Module | ActiveDirectoryDsc.Common + Resolve-DomainFQDN | ActiveDirectoryDsc.Common + New-InvalidOperationException | ActiveDirectoryDsc.Common + ConvertTo-DeploymentForestMode | ActiveDirectoryDsc.Common + ConvertTo-DeploymentDomainMode | ActiveDirectoryDsc.Common #> function Get-TargetResource { @@ -115,172 +60,136 @@ function Get-TargetResource [Parameter()] [ValidateNotNullOrEmpty()] [System.String] - $ParentDomainName, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $DomainNetBiosName, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.Management.Automation.PSCredential] - $DnsDelegationCredential, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $DatabasePath, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $LogPath, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $SysvolPath, - - [Parameter()] - [ValidateSet('Win2008', 'Win2008R2', 'Win2012', 'Win2012R2', 'WinThreshold')] - [System.String] - $ForestMode, - - [Parameter()] - [ValidateSet('Win2008', 'Win2008R2', 'Win2012', 'Win2012R2', 'WinThreshold')] - [System.String] - $DomainMode + $ParentDomainName ) Assert-Module -ModuleName 'ADDSDeployment' -ImportModule + $domainFQDN = Resolve-DomainFQDN -DomainName $DomainName -ParentDomainName $ParentDomainName - $returnValue = @{ - DomainName = $DomainName - Credential = $Credential - SafeModeAdministratorPassword = $SafeModeAdministratorPassword - ParentDomainName = $null - DomainNetBiosName = $null - DnsDelegationCredential = $null - DatabasePath = $null - LogPath = $null - SysvolPath = $null - ForestMode = $null - DomainMode = $null - DomainExist = $false - Forest = $null - DnsRoot = $null + # If the domain has been installed then the Netlogon SysVol registry item will exist. + $domainShouldBePresent = $true + try + { + $sysvolPath = Get-ItemPropertyValue -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' -Name 'SysVol' + } + catch + { + $domainShouldBePresent = $false } - $domainFQDN = Resolve-DomainFQDN -DomainName $DomainName -ParentDomainName $ParentDomainName + if ($domainShouldBePresent) + { + # Test that the correct domain SysVol path exists + $domainSysVolPath = Join-Path -Path $sysvolPath -ChildPath $domainFQDN - $retries = 0 - $maxRetries = 5 - $retryIntervalInSeconds = 30 + if (-not (Test-Path -Path $domainSysVolPath)) + { + $errorMessage = $script:localizedData.SysVolPathDoesNotExistError -f $domainSysVolPath + New-InvalidOperationException -Message $errorMessage + } - <# - If the domain was created on this node, then the tracking file will be - present which means we should wait for the domain to be available. - #> - $domainShouldExist = Test-Path -Path (Get-TrackingFilename -DomainName $DomainName) - $domainFound = $false + Write-Verbose ($script:localizedData.QueryDomain -f $domainFQDN) - do - { - try + $retries = 0 + $maxRetries = 15 + $retryIntervalInSeconds = 30 + + do { - if (Test-DomainMember) + $domainFound = $true + try { - # We're already a domain member, so take the credentials out of the equation - Write-Verbose ($script:localizedData.QueryDomainWithLocalCredential -f $domainFQDN) - - $domain = Get-ADDomain -Identity $domainFQDN -ErrorAction Stop - $forest = Get-ADForest -Identity $domain.Forest -ErrorAction Stop + $domain = Get-ADDomain -Identity $domainFQDN -Server localhost -ErrorAction Stop } - else + catch [Microsoft.ActiveDirectory.Management.ADServerDownException] { - Write-Verbose ($script:localizedData.QueryDomainWithCredential -f $domainFQDN) - - $domain = Get-ADDomain -Identity $domainFQDN -Credential $Credential -ErrorAction Stop - $forest = Get-ADForest -Identity $domain.Forest -Credential $Credential -ErrorAction Stop + Write-Verbose ($script:localizedData.ADServerDownOrFault -f $domainFQDN) + $domainFound = $false + # will fall into the retry mechanism. + } + catch + { + $errorMessage = $script:localizedData.GetAdDomainUnexpectedError -f $domainFQDN + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ } - <# - If we don't throw an exception, the domain is already UP (on this - node or some other domain controller that responded) and this - resource shouldn't run. - #> - Write-Verbose ($script:localizedData.DomainFound -f $domain.DnsRoot) - - $returnValue['DnsRoot'] = $domain.DnsRoot - $returnValue['Forest'] = $forest.Name - $returnValue['ParentDomainName'] = $domain.ParentDomain - $returnValue['DomainNetBiosName'] = $domain.NetBIOSName - $returnValue['ForestMode'] = (ConvertTo-DeploymentForestMode -Mode $forest.ForestMode) -as [System.String] - $returnValue['DomainMode'] = (ConvertTo-DeploymentDomainMode -Mode $domain.DomainMode) -as [System.String] - $returnValue['DomainExist'] = $true + if (-not $domainFound) + { + $retries++ - $domainFound = $true + Write-Verbose ($script:localizedData.RetryingGetADDomain -f + $retries, $maxRetries, $retryIntervalInSeconds) - if (-not $domainShouldExist) - { - Write-Warning -Message ( - $script:localizedData.MissingTrackingFile -f (Get-TrackingFilename -DomainName $DomainName) - ) + Start-Sleep -Seconds $retryIntervalInSeconds } - } - catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] + } while ((-not $domainFound) -and $retries -lt $maxRetries) + + if ($retries -eq $maxRetries) { - <# - This is thrown when the node is a domain member, meaning the node - is able to evaluate if there is a domain controller managing the - domain name (which is different that its own domain). - That means this node cannot be provisioned as a domain controller - for another domain name. - #> - $errorMessage = $script:localizedData.ExistingDomainMemberError -f $DomainName - New-ObjectNotFoundException -Message $errorMessage -ErrorRecord $_ + $errorMessage = $script:localizedData.MaxDomainRetriesReachedError -f $domainFQDN + New-InvalidOperationException -Message $errorMessage } - catch [Microsoft.ActiveDirectory.Management.ADServerDownException] - { - Write-Verbose ($script:localizedData.DomainNotFound -f $domainFQDN) + } + else + { + $domain = $null + } - # will fall into retry mechanism if the domain should exist. - } - catch [System.Security.Authentication.AuthenticationException] + if ($domain) + { + Write-Verbose ($script:localizedData.DomainFound -f $domain.DnsRoot) + + try { - $errorMessage = $script:localizedData.InvalidCredentialError -f $DomainName - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + $forest = Get-ADForest -Identity $domain.Forest -Server localhost -ErrorAction Stop } catch { - $errorMessage = $script:localizedData.UnhandledError -f ($_.Exception | Format-List -Force | Out-String) - Write-Verbose $errorMessage - - if ($domainShouldExist -and ($_.Exception.InnerException -is [System.ServiceModel.FaultException])) - { - Write-Verbose $script:localizedData.FaultExceptionAndDomainShouldExist - - # will fall into retry mechanism if the domain should exist. - } - else - { - # Not sure what's gone on here! - throw $_ - } + $errorMessage = $script:localizedData.GetAdForestUnexpectedError -f $domain.Forest + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ } - if (-not $domainFound -and $domainShouldExist) - { - $retries++ - - $waitSeconds = $retries * $retryIntervalInSeconds - - Write-Verbose ($script:localizedData.RetryingGetADDomain -f $retries, $maxRetries, $waitSeconds) - - Start-Sleep -Seconds $waitSeconds + $deploymentForestMode = (ConvertTo-DeploymentForestMode -Mode $forest.ForestMode) -as [System.String] + $deploymentDomainMode = (ConvertTo-DeploymentDomainMode -Mode $domain.DomainMode) -as [System.String] + $serviceNTDS = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Parameters' + $serviceNETLOGON = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' + + $returnValue = @{ + DomainName = $DomainName + Credential = $Credential + SafeModeAdministratorPassword = $SafeModeAdministratorPassword + ParentDomainName = $domain.ParentDomain + DomainNetBiosName = $domain.NetBIOSName + DnsDelegationCredential = $null + DatabasePath = $serviceNTDS.'DSA Working Directory' + LogPath = $serviceNTDS.'Database log files path' + SysvolPath = $serviceNETLOGON.SysVol + ForestMode = $deploymentForestMode + DomainMode = $deploymentDomainMode + DomainExist = $true + Forest = $forest.Name + DnsRoot = $domain.DnsRoot + } + } + else + { + $returnValue = @{ + DomainName = $DomainName + Credential = $Credential + SafeModeAdministratorPassword = $SafeModeAdministratorPassword + ParentDomainName = $ParentDomainName + DomainNetBiosName = $null + DnsDelegationCredential = $null + DatabasePath = $null + LogPath = $null + SysvolPath = $null + ForestMode = $null + DomainMode = $null + DomainExist = $false + Forest = $null + DnsRoot = $null } - } while ((-not $domainFound -and $domainShouldExist) -and $retries -lt $maxRetries) + } return $returnValue } #end function Get-TargetResource @@ -386,62 +295,39 @@ function Test-TargetResource $DomainMode ) - $targetResource = Get-TargetResource @PSBoundParameters - - $isCompliant = $true - - <# - The Get-Target resource returns DomainName as the domain's FQDN. Therefore, we - need to resolve this before comparison. - #> - $domainFQDN = Resolve-DomainFQDN -DomainName $DomainName -ParentDomainName $ParentDomainName - - if ($domainFQDN -ne $targetResource.DnsRoot) - { - Write-Verbose -Message ( - $script:localizedData.ExpectedDomain -f $domainFQDN - ) - - $isCompliant = $false + $getTargetResourceParameters = @{ + DomainName = $DomainName + Credential = $Credential + SafeModeAdministratorPassword = $SafeModeAdministratorPassword + ParentDomainName = $ParentDomainName } - $propertyNames = @( - 'ParentDomainName', - 'DomainNetBiosName' - ) - - foreach ($propertyName in $propertyNames) - { - if ($PSBoundParameters.ContainsKey($propertyName)) - { - $propertyValue = (Get-Variable -Name $propertyName).Value - - if ($targetResource.$propertyName -ne $propertyValue) + @($getTargetResourceParameters.Keys) | + ForEach-Object { + if (-not $PSBoundParameters.ContainsKey($_)) { - Write-Verbose -Message ( - $script:localizedData.PropertyValueIncorrect ` - -f $propertyName, $propertyValue, $targetResource.$propertyName - ) - - $isCompliant = $false + $getTargetResourceParameters.Remove($_) } } - } - if ($isCompliant) + $targetResource = Get-TargetResource @getTargetResourceParameters + + $domainFQDN = Resolve-DomainFQDN -DomainName $DomainName -ParentDomainName $ParentDomainName + + if ($targetResource.DomainExist) { - Write-Verbose -Message ( - $script:localizedData.DomainInDesiredState -f $domainFQDN - ) + Write-Verbose -Message ($script:localizedData.DomainInDesiredState -f + $domainFQDN) + $inDesiredState = $true } else { - Write-Verbose -Message ( - $script:localizedData.DomainNotInDesiredState -f $domainFQDN - ) + Write-Verbose -Message ($script:localizedData.DomainNotInDesiredState -f + $domainFQDN) + $inDesiredState = $false } - return $isCompliant + return $inDesiredState } #end function Test-TargetResource <# @@ -554,8 +440,22 @@ function Set-TargetResource # Debug can pause Install-ADDSForest/Install-ADDSDomain, so we remove it. $null = $PSBoundParameters.Remove('Debug') - # Not entirely necessary, but run Get-TargetResource to ensure we raise any pre-flight errors. - $targetResource = Get-TargetResource @PSBoundParameters + $getTargetResourceParameters = @{ + DomainName = $DomainName + Credential = $Credential + SafeModeAdministratorPassword = $SafeModeAdministratorPassword + ParentDomainName = $ParentDomainName + } + + @($getTargetResourceParameters.Keys) | + ForEach-Object { + if (-not $PSBoundParameters.ContainsKey($_)) + { + $getTargetResourceParameters.Remove($_) + } + } + + $targetResource = Get-TargetResource @getTargetResourceParameters if (-not $targetResource.DomainExist) { @@ -629,8 +529,6 @@ function Set-TargetResource Write-Verbose -Message ($script:localizedData.CreatedForest -f $DomainName) } - 'Finished' | Out-File -FilePath (Get-TrackingFilename -DomainName $DomainName) -Force - <# Signal to the LCM to reboot the node to compensate for the one we suppressed from Install-ADDSForest/Install-ADDSDomain. diff --git a/source/DSCResources/MSFT_ADDomain/en-US/MSFT_ADDomain.strings.psd1 b/source/DSCResources/MSFT_ADDomain/en-US/MSFT_ADDomain.strings.psd1 index f9078e76f..f454e8f9f 100644 --- a/source/DSCResources/MSFT_ADDomain/en-US/MSFT_ADDomain.strings.psd1 +++ b/source/DSCResources/MSFT_ADDomain/en-US/MSFT_ADDomain.strings.psd1 @@ -1,21 +1,19 @@ # culture="en-US" ConvertFrom-StringData @' - ExistingDomainMemberError = Computer is already a domain member. Cannot create a new '{0}' domain on this computer. (ADD0001) - InvalidCredentialError = Domain '{0}' is available, but invalid credentials were supplied. (ADD0002) - QueryDomainWithLocalCredential = Computer is a domain member; querying domain '{0}' using local credential. (ADD0003) - QueryDomainWithCredential = Computer is a workgroup member; querying for domain '{0}' using supplied credential. (ADD0004) - DomainFound = Active Directory domain '{0}' found. (ADD0005) - DomainNotFound = Active Directory domain '{0}' cannot be found. (ADD0006) - CreatingChildDomain = Creating domain '{0}' as a child of domain '{1}'. (ADD0007) - CreatedChildDomain = Child domain '{0}' created. (ADD0008) - CreatingForest = Creating AD forest '{0}'. (ADD0009) - CreatedForest = AD forest '{0}' created. (ADD0010) - PropertyValueIncorrect = The value of the property '{0}' is incorrect. Expected the value '{1}', but was '{2}'. (ADD0011) - DomainInDesiredState = The domain '{0}' is in the desired state. (ADD0012) - DomainNotInDesiredState = The domain '{0}' is NOT in the desired state. (ADD0013) - RetryingGetADDomain = Attempt {0} of {1} to call Get-ADDomain failed, retrying in {2} seconds. (ADD0014) - UnhandledError = Unhandled error occurred, detail here: {0} (ADD0015) - FaultExceptionAndDomainShouldExist = ServiceModel FaultException detected and domain should exist, performing retry. (ADD0016) - MissingTrackingFile = The domain exists but the tracking file '{0}' could not be found. This can make the resource try to recreate the domain in some circumstances, for example if LCM is quicker to start than the domain is when the node restarts. Please recreate the tracking file by running `'Finished' | Out-File -FilePath '{0}' -Force`. (ADD0017) - ExpectedDomain = Expected to find the domain '{0}', but it was not found. (ADD0016) + QueryDomain = Querying for domain '{0}'. (ADD0001) + ADServerDown = The AD Server for domain '{0}' is currently down. (ADD0002) + DomainFound = Active Directory domain '{0}' found. (ADD0003) + DomainNotFound = Active Directory domain '{0}' cannot be found. (ADD0004) + CreatingChildDomain = Creating domain '{0}' as a child of domain '{1}'. (ADD0005) + CreatedChildDomain = Child domain '{0}' created. (ADD0006) + CreatingForest = Creating AD forest '{0}'. (ADD0007) + CreatedForest = AD forest '{0}' created. (ADD0008) + DomainInDesiredState = The domain '{0}' is in the desired state. (ADD0009) + DomainNotInDesiredState = The domain '{0}' is NOT in the desired state. (ADD0010) + RetryingGetADDomain = Attempt {0} of {1} to call Get-ADDomain failed, retrying in {2} seconds. (ADD0011) + ExpectedDomain = Expected to find the domain '{0}', but it was not found. (ADD0012) + SysVolPathDoesNotExistError = The expected SysVol Path '{0}' does not exist. (ADD0013) + MaxDomainRetriesReachedError = Maximum Get-ADDomain retries reached and the domain did not respond. (ADD0014) + GetAdDomainUnexpectedError = Error getting AD domain '{0}'. (ADD0015) + GetAdForestUnexpectedError = Error getting AD forest '{0}'. (ADD0016) '@ From d66073a6675321b6e009947d3a41671e06c39cf3 Mon Sep 17 00:00:00 2001 From: Simon Heather Date: Sat, 8 Feb 2020 21:14:24 +0000 Subject: [PATCH 02/13] Update ADDomain Example --- .../Resources/ADDomain/2-ADDomain_NewChildDomain_Config.ps1 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/Examples/Resources/ADDomain/2-ADDomain_NewChildDomain_Config.ps1 b/source/Examples/Resources/ADDomain/2-ADDomain_NewChildDomain_Config.ps1 index e60a2d616..d1d0eb4f4 100644 --- a/source/Examples/Resources/ADDomain/2-ADDomain_NewChildDomain_Config.ps1 +++ b/source/Examples/Resources/ADDomain/2-ADDomain_NewChildDomain_Config.ps1 @@ -18,7 +18,9 @@ Updated author, copyright notice, and URLs. <# .DESCRIPTION This configuration will create a new child domain in an existing forest with - a Domain Functional Level of Windows Server 2012R2. + a Domain Functional Level of Windows Server 2016 (WinThreshold). + The credential parameter must contain the domain qualified credentials of a + user in the forest who has permissions to create a new child domain. #> Configuration ADDomain_NewChildDomain_Config { @@ -57,7 +59,7 @@ Configuration ADDomain_NewChildDomain_Config DomainName = 'child' Credential = $Credential SafemodeAdministratorPassword = $SafeModePassword - DomainMode = 'Win2012R2' + DomainMode = 'WinThreshold' ParentDomainName = 'contoso.com' } } From fa0cf7fda7384c638e33c1544b0ca2adb7550afc Mon Sep 17 00:00:00 2001 From: Simon Heather Date: Sat, 8 Feb 2020 21:14:47 +0000 Subject: [PATCH 03/13] Change Minimum PowerShellVersion to 5.0 --- source/ActiveDirectoryDsc.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/ActiveDirectoryDsc.psd1 b/source/ActiveDirectoryDsc.psd1 index 4ed54ce61..a1e370101 100644 --- a/source/ActiveDirectoryDsc.psd1 +++ b/source/ActiveDirectoryDsc.psd1 @@ -20,7 +20,7 @@ Description = 'The ActiveDirectoryDsc module contains DSC resources for deployme These DSC resources allow you to configure new domains, child domains, and high availability domain controllers, establish cross-domain trusts and manage users, groups and OUs.' # Minimum version of the Windows PowerShell engine required by this module -PowerShellVersion = '4.0' +PowerShellVersion = '5.0' # Minimum version of the common language runtime (CLR) required by this module CLRVersion = '4.0' From 8a7c2ec13c8a1b9069e49c3b44bfa9f7be9f98ca Mon Sep 17 00:00:00 2001 From: Simon Heather Date: Sat, 8 Feb 2020 21:24:35 +0000 Subject: [PATCH 04/13] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd2275221..4b9253f13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ For older change log history see the [historic changelog](HISTORIC_CHANGELOG.md) - ActiveDirectoryDsc - Updated Azure Pipeline Windows image ([issue #551](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/551)). - Updated license copyright ([issue #550](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/550)). +- ADDomain + - Change Domain Install Tracking File to NetLogon Registry Test and Refactor. ([issue #560](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/560)). - ADForestProperties - Refactored unit tests. From be6962318540c9fc645ec66cb4ca8a72488f4d6e Mon Sep 17 00:00:00 2001 From: Simon Heather Date: Sat, 8 Feb 2020 21:40:52 +0000 Subject: [PATCH 05/13] Fix ADDomain strings --- .../MSFT_ADDomain/MSFT_ADDomain.psm1 | 2 +- .../en-US/MSFT_ADDomain.strings.psd1 | 24 +++++++++---------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.psm1 b/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.psm1 index bdd9a1431..c6a147b60 100644 --- a/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.psm1 +++ b/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.psm1 @@ -103,7 +103,7 @@ function Get-TargetResource } catch [Microsoft.ActiveDirectory.Management.ADServerDownException] { - Write-Verbose ($script:localizedData.ADServerDownOrFault -f $domainFQDN) + Write-Verbose ($script:localizedData.ADServerDown -f $domainFQDN) $domainFound = $false # will fall into the retry mechanism. } diff --git a/source/DSCResources/MSFT_ADDomain/en-US/MSFT_ADDomain.strings.psd1 b/source/DSCResources/MSFT_ADDomain/en-US/MSFT_ADDomain.strings.psd1 index f454e8f9f..100d45304 100644 --- a/source/DSCResources/MSFT_ADDomain/en-US/MSFT_ADDomain.strings.psd1 +++ b/source/DSCResources/MSFT_ADDomain/en-US/MSFT_ADDomain.strings.psd1 @@ -3,17 +3,15 @@ ConvertFrom-StringData @' QueryDomain = Querying for domain '{0}'. (ADD0001) ADServerDown = The AD Server for domain '{0}' is currently down. (ADD0002) DomainFound = Active Directory domain '{0}' found. (ADD0003) - DomainNotFound = Active Directory domain '{0}' cannot be found. (ADD0004) - CreatingChildDomain = Creating domain '{0}' as a child of domain '{1}'. (ADD0005) - CreatedChildDomain = Child domain '{0}' created. (ADD0006) - CreatingForest = Creating AD forest '{0}'. (ADD0007) - CreatedForest = AD forest '{0}' created. (ADD0008) - DomainInDesiredState = The domain '{0}' is in the desired state. (ADD0009) - DomainNotInDesiredState = The domain '{0}' is NOT in the desired state. (ADD0010) - RetryingGetADDomain = Attempt {0} of {1} to call Get-ADDomain failed, retrying in {2} seconds. (ADD0011) - ExpectedDomain = Expected to find the domain '{0}', but it was not found. (ADD0012) - SysVolPathDoesNotExistError = The expected SysVol Path '{0}' does not exist. (ADD0013) - MaxDomainRetriesReachedError = Maximum Get-ADDomain retries reached and the domain did not respond. (ADD0014) - GetAdDomainUnexpectedError = Error getting AD domain '{0}'. (ADD0015) - GetAdForestUnexpectedError = Error getting AD forest '{0}'. (ADD0016) + CreatingChildDomain = Creating domain '{0}' as a child of domain '{1}'. (ADD0004) + CreatedChildDomain = Child domain '{0}' created. (ADD0005) + CreatingForest = Creating AD forest '{0}'. (ADD0006) + CreatedForest = AD forest '{0}' created. (ADD0007) + DomainInDesiredState = The domain '{0}' is in the desired state. (ADD0008) + DomainNotInDesiredState = The domain '{0}' is NOT in the desired state. (ADD0009) + RetryingGetADDomain = Attempt {0} of {1} to call Get-ADDomain failed, retrying in {2} seconds. (ADD0010) + SysVolPathDoesNotExistError = The expected SysVol Path '{0}' does not exist. (ADD0011) + MaxDomainRetriesReachedError = Maximum Get-ADDomain retries reached and the domain did not respond. (ADD0012) + GetAdDomainUnexpectedError = Error getting AD domain '{0}'. (ADD0013) + GetAdForestUnexpectedError = Error getting AD forest '{0}'. (ADD0014) '@ From 6298335e0e7d8f053149e9a575663618a6e8ecc7 Mon Sep 17 00:00:00 2001 From: Simon Heather Date: Sun, 9 Feb 2020 19:19:10 +0000 Subject: [PATCH 06/13] Add integration tests --- .../MSFT_ADDomain.Child.Integration.Tests.ps1 | 112 ++++++++++++++++++ .../MSFT_ADDomain.Child.config.ps1 | 100 ++++++++++++++++ .../MSFT_ADDomain.Root.Integration.Tests.ps1 | 112 ++++++++++++++++++ .../Integration/MSFT_ADDomain.Root.config.ps1 | 100 ++++++++++++++++ 4 files changed, 424 insertions(+) create mode 100644 Tests/Integration/MSFT_ADDomain.Child.Integration.Tests.ps1 create mode 100644 Tests/Integration/MSFT_ADDomain.Child.config.ps1 create mode 100644 Tests/Integration/MSFT_ADDomain.Root.Integration.Tests.ps1 create mode 100644 Tests/Integration/MSFT_ADDomain.Root.config.ps1 diff --git a/Tests/Integration/MSFT_ADDomain.Child.Integration.Tests.ps1 b/Tests/Integration/MSFT_ADDomain.Child.Integration.Tests.ps1 new file mode 100644 index 000000000..54ed63de4 --- /dev/null +++ b/Tests/Integration/MSFT_ADDomain.Child.Integration.Tests.ps1 @@ -0,0 +1,112 @@ +<# + .SYNOPSIS + Pester integration test for the ADDomain Resource of the ActiveDirectoryDsc Module + This Subtest creates a child domain in an existing forest + + .DESCRIPTION + Verbose/Debug output can be set by running: + + Invoke-pester -Script @{Path='.\MSFT_ADDomain.Child.Integration.Tests.ps1';Parameters=@{Verbose=$true;Debug=$true}} +#> + +[CmdletBinding()] +param () + +Set-StrictMode -Version 1.0 + +$script:dscModuleName = 'ActiveDirectoryDsc' +$script:dscResourceFriendlyName = 'ADDomain' +$script:dscResourceName = "MSFT_$($script:dscResourceFriendlyName)" +$script:subTestName = 'Child' + +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).$($script:subTestName).config.ps1" + . $configFile + + Describe "$($script:dscResourceName).$($script:subTestName)_Integration" { + BeforeAll { + $resourceId = "[$($script:dscResourceFriendlyName)]Integration_Test" + } + + + foreach ($testName in $ConfigurationData.AllNodes.Tests.Keys ) + { + $configurationName = "$($script:dscResourceName)_$($testName)_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + $DscConfigurationStatus = Get-DscConfigurationStatus + if ($DscConfigurationStatus.RebootRequested) + { + Write-Warning 'A Reboot has been requested by the DSC. Please reboot then re-run the test' + Return + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -ErrorAction Stop + } | Should -Not -Throw + } + + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + foreach ($property in $ConfigurationData.AllNodes.Tests.$testName.Keys) + { + It "Should have set the correct $property property" { + $resourceCurrentState.$property | Should -Be $ConfigurationData.AllNodes.Tests.$testName.$property + } + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration | Should -Be 'True' + } + } + } + } +} +finally +{ + #region FOOTER + Restore-TestEnvironment -TestEnvironment $script:testEnvironment + #endregion +} diff --git a/Tests/Integration/MSFT_ADDomain.Child.config.ps1 b/Tests/Integration/MSFT_ADDomain.Child.config.ps1 new file mode 100644 index 000000000..e82f08663 --- /dev/null +++ b/Tests/Integration/MSFT_ADDomain.Child.config.ps1 @@ -0,0 +1,100 @@ +#region HEADER +# Integration Test Config Template Version: 1.2.0 +#endregion + +$configFile = [System.IO.Path]::ChangeExtension($MyInvocation.MyCommand.Path, 'json') +if (Test-Path -Path $configFile) +{ + <# + Allows reading the configuration data from a JSON file, for real testing + scenarios outside of the CI. + #> + $ConfigurationData = Get-Content -Path $configFile | ConvertFrom-Json +} +else +{ + $ConfigurationData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + CertificateFile = $env:DscPublicCertificatePath + CredentialUserName = 'administrator@contoso.com' + CredentialPassword = 'password' + SafeModePassword = 'SafemodePassword@1' + Tests = [Ordered]@{ + FeatureInstall = @{ } + ForestChildDomain = @{ + DomainName = 'child' + ParentDomainName = 'contoso.com' + DomainNetbiosName = 'CHILD-CONTOSO' + DatabasePath = 'C:\NTDS' + LogPath = 'C:\NTDS' + SysvolPath = 'C:\SysVol' + DomainMode = 'WinThreshold' + } + } + } + ) + } +} + +<# + .SYNOPSIS + Initialise Config +#> +Configuration MSFT_ADDomain_FeatureInstall_Config +{ + Import-DscResource -ModuleName 'PSDesiredStateConfiguration' + + $testName = 'FeatureInstall' + + node $AllNodes.NodeName + { + WindowsFeature 'ADDS' + { + Name = 'AD-Domain-Services' + } + } +} + +<# + .SYNOPSIS + Initialise Config +#> +Configuration MSFT_ADDomain_ForestChildDomain_Config +{ + Import-DscResource -ModuleName 'ActiveDirectoryDsc' + + $testName = 'ForestChildDomain' + + node $AllNodes.NodeName + { + $SecureCredentialPassword = ConvertTo-SecureString ` + -String $Node.CredentialPassword ` + -AsPlainText -Force + + $Credential = [System.Management.Automation.PSCredential]::new( + $Node.CredentialUserName, + $SecureCredentialPassword + ) + + $SafeModePassword = ConvertTo-SecureString ` + -String $Node.SafeModePassword ` + -AsPlainText -Force + + $SafemodeCredential = [System.Management.Automation.PSCredential]::new('n/a', $SafemodePassword) + + ADDomain Integration_Test + { + DomainName = $Node.Tests.$testName.DomainName + ParentDomainName = $Node.Tests.$testName.ParentDomainName + Credential = $Credential + SafemodeAdministratorPassword = $SafeModeCredential + DomainNetbiosName = $Node.Tests.$testName.DomainNetbiosName + DatabasePath = $Node.Tests.$testName.DatabasePath + LogPath = $Node.Tests.$testName.LogPath + SysvolPath = $Node.Tests.$testName.SysvolPath + DomainMode = $Node.Tests.$testName.DomainMode + } + } +} diff --git a/Tests/Integration/MSFT_ADDomain.Root.Integration.Tests.ps1 b/Tests/Integration/MSFT_ADDomain.Root.Integration.Tests.ps1 new file mode 100644 index 000000000..0c8d63937 --- /dev/null +++ b/Tests/Integration/MSFT_ADDomain.Root.Integration.Tests.ps1 @@ -0,0 +1,112 @@ +<# + .SYNOPSIS + Pester integration test for the ADDomain Resource of the ActiveDirectoryDsc Module + This Subtest creates a root domain in a new forest + + .DESCRIPTION + Verbose/Debug output can be set by running: + + Invoke-pester -Script @{Path='.\MSFT_ADDomain.Root.Integration.Tests.ps1';Parameters=@{Verbose=$true;Debug=$true}} +#> + +[CmdletBinding()] +param () + +Set-StrictMode -Version 1.0 + +$script:dscModuleName = 'ActiveDirectoryDsc' +$script:dscResourceFriendlyName = 'ADDomain' +$script:dscResourceName = "MSFT_$($script:dscResourceFriendlyName)" +$script:subTestName = 'Root' + +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).$($script:subTestName).config.ps1" + . $configFile + + Describe "$($script:dscResourceName).$($script:subTestName)_Integration" { + BeforeAll { + $resourceId = "[$($script:dscResourceFriendlyName)]Integration_Test" + } + + + foreach ($testName in $ConfigurationData.AllNodes.Tests.Keys ) + { + $configurationName = "$($script:dscResourceName)_$($testName)_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + $DscConfigurationStatus = Get-DscConfigurationStatus + if ($DscConfigurationStatus.RebootRequested) + { + Write-Warning 'A Reboot has been requested by the DSC. Please reboot then re-run the test' + Return + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -ErrorAction Stop + } | Should -Not -Throw + } + + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + foreach ($property in $ConfigurationData.AllNodes.Tests.$testName.Keys) + { + It "Should have set the correct $property property" { + $resourceCurrentState.$property | Should -Be $ConfigurationData.AllNodes.Tests.$testName.$property + } + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration | Should -Be 'True' + } + } + } + } +} +finally +{ + #region FOOTER + Restore-TestEnvironment -TestEnvironment $script:testEnvironment + #endregion +} diff --git a/Tests/Integration/MSFT_ADDomain.Root.config.ps1 b/Tests/Integration/MSFT_ADDomain.Root.config.ps1 new file mode 100644 index 000000000..a2d8745a1 --- /dev/null +++ b/Tests/Integration/MSFT_ADDomain.Root.config.ps1 @@ -0,0 +1,100 @@ +#region HEADER +# Integration Test Config Template Version: 1.2.0 +#endregion + +$configFile = [System.IO.Path]::ChangeExtension($MyInvocation.MyCommand.Path, 'json') +if (Test-Path -Path $configFile) +{ + <# + Allows reading the configuration data from a JSON file, for real testing + scenarios outside of the CI. + #> + $ConfigurationData = Get-Content -Path $configFile | ConvertFrom-Json +} +else +{ + $ConfigurationData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + CertificateFile = $env:DscPublicCertificatePath + CredentialUserName = 'administrator' + CredentialPassword = 'ContosoAdmin@1' + SafeModePassword = 'SafemodePassword@1' + Tests = [Ordered]@{ + FeatureInstall = @{ } + ForestRootDomain = @{ + DomainName = 'contoso.com' + DomainNetbiosName = 'CONTOSO' + DatabasePath = 'C:\NTDS' + LogPath = 'C:\NTDS' + SysvolPath = 'C:\SysVol' + ForestMode = 'WinThreshold' + DomainMode = 'WinThreshold' + } + } + } + ) + } +} + +<# + .SYNOPSIS + Initialise Config +#> +Configuration MSFT_ADDomain_FeatureInstall_Config +{ + Import-DscResource -ModuleName 'PSDesiredStateConfiguration' + + $testName = 'FeatureInstall' + + node $AllNodes.NodeName + { + WindowsFeature 'ADDS' + { + Name = 'AD-Domain-Services' + } + } +} + +<# + .SYNOPSIS + Initialise Config +#> +Configuration MSFT_ADDomain_ForestRootDomain_Config +{ + Import-DscResource -ModuleName 'ActiveDirectoryDsc' + + $testName = 'ForestRootDomain' + + node $AllNodes.NodeName + { + $SecureCredentialPassword = ConvertTo-SecureString ` + -String $Node.CredentialPassword ` + -AsPlainText -Force + + $Credential = [System.Management.Automation.PSCredential]::new( + $Node.CredentialUserName, + $SecureCredentialPassword + ) + + $SafeModePassword = ConvertTo-SecureString ` + -String $Node.SafeModePassword ` + -AsPlainText -Force + + $SafemodeCredential = [System.Management.Automation.PSCredential]::new('n/a', $SafemodePassword) + + ADDomain Integration_Test + { + DomainName = $Node.Tests.$testName.DomainName + Credential = $Credential + SafemodeAdministratorPassword = $SafeModeCredential + DomainNetbiosName = $Node.Tests.$testName.DomainNetbiosName + DatabasePath = $Node.Tests.$testName.DatabasePath + LogPath = $Node.Tests.$testName.LogPath + SysvolPath = $Node.Tests.$testName.SysvolPath + ForestMode = $Node.Tests.$testName.ForestMode + DomainMode = $Node.Tests.$testName.DomainMode + } + } +} From ffc278ee6ad5199f64d279765248d12943390280 Mon Sep 17 00:00:00 2001 From: Simon Heather Date: Sun, 9 Feb 2020 19:19:48 +0000 Subject: [PATCH 07/13] Fix SysvolPath --- source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.psm1 b/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.psm1 index c6a147b60..8335803f4 100644 --- a/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.psm1 +++ b/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.psm1 @@ -163,7 +163,7 @@ function Get-TargetResource DnsDelegationCredential = $null DatabasePath = $serviceNTDS.'DSA Working Directory' LogPath = $serviceNTDS.'Database log files path' - SysvolPath = $serviceNETLOGON.SysVol + SysvolPath = $serviceNETLOGON.SysVol -replace '\\sysvol$', '' ForestMode = $deploymentForestMode DomainMode = $deploymentDomainMode DomainExist = $true From e2d17364551bd46a2ea7322d9e0ea2c52ab13245 Mon Sep 17 00:00:00 2001 From: Simon Heather Date: Sun, 9 Feb 2020 19:22:38 +0000 Subject: [PATCH 08/13] Improve Credential parameter description --- .../MSFT_ADDomain/MSFT_ADDomain.psm1 | 33 +++++++++---------- .../MSFT_ADDomain/MSFT_ADDomain.schema.mof | 2 +- .../en-US/about_ADDomain.help.txt | 2 +- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.psm1 b/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.psm1 index 8335803f4..0739971bc 100644 --- a/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.psm1 +++ b/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.psm1 @@ -15,12 +15,11 @@ $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_ADDomain' child domain this must be set to a single-label DNS name. .PARAMETER Credential - Specifies the user name and password that corresponds to the account used - to install the domain controller. When adding a child domain these credentials - need the correct permission in the parent domain. The credentials will also - be used to query for the existence of the domain or child domain. This will - not be created as a user in the new domain. The domain administrator password - will be the same as the password of the local Administrator of this node. + Specifies the user name and password that corresponds to the account used to install + the domain controller. These are only used when adding a child domain and these credentials + need the correct permission in the parent domain. This will not be created as a user in the + new domain. The domain administrator password will be the same as the password of the local + Administrator of this node. .PARAMETER SafeModeAdministratorPassword Password for the administrator account when the computer is started in Safe Mode. @@ -203,12 +202,11 @@ function Get-TargetResource child domain this must be set to a single-label DNS name. .PARAMETER Credential - Specifies the user name and password that corresponds to the account used - to install the domain controller. When adding a child domain these credentials - need the correct permission in the parent domain. The credentials will also - be used to query for the existence of the domain or child domain. This will - not be created as a user in the new domain. The domain administrator password - will be the same as the password of the local Administrator of this node. + Specifies the user name and password that corresponds to the account used to install + the domain controller. These are only used when adding a child domain and these credentials + need the correct permission in the parent domain. This will not be created as a user in the + new domain. The domain administrator password will be the same as the password of the local + Administrator of this node. .PARAMETER SafeModeAdministratorPassword Password for the administrator account when the computer is started in Safe Mode. @@ -339,12 +337,11 @@ function Test-TargetResource child domain this must be set to a single-label DNS name. .PARAMETER Credential - Specifies the user name and password that corresponds to the account used - to install the domain controller. When adding a child domain these credentials - need the correct permission in the parent domain. The credentials will also - be used to query for the existence of the domain or child domain. This will - not be created as a user in the new domain. The domain administrator password - will be the same as the password of the local Administrator of this node. + Specifies the user name and password that corresponds to the account used to install + the domain controller. These are only used when adding a child domain and these credentials + need the correct permission in the parent domain. This will not be created as a user in the + new domain. The domain administrator password will be the same as the password of the local + Administrator of this node. .PARAMETER SafeModeAdministratorPassword Password for the administrator account when the computer is started in Safe Mode. diff --git a/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.schema.mof b/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.schema.mof index ad5aeadde..542c9fe6c 100644 --- a/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.schema.mof +++ b/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.schema.mof @@ -2,7 +2,7 @@ class MSFT_ADDomain : OMI_BaseResource { [Key, Description("The fully qualified domain name (FQDN) of a new domain. If setting up a child domain this must be set to a single-label DNS name.")] String DomainName; - [Required, Description("Specifies the user name and password that corresponds to the account used to install the domain controller. When adding a child domain these credentials need the correct permission in the parent domain. The credentials will also be used to query for the existence of the domain or child domain. This will not be created as a user in the new domain. The domain administrator password will be the same as the password of the local Administrator of this node."), EmbeddedInstance("MSFT_Credential")] String Credential; + [Required, Description("Specifies the user name and password that corresponds to the account used to install the domain controller. These are only used when adding a child domain and these credentials need the correct permission in the parent domain. This will not be created as a user in the new domain. The domain administrator password will be the same as the password of the local Administrator of this node."), EmbeddedInstance("MSFT_Credential")] String Credential; [Required, Description("Password for the administrator account when the computer is started in Safe Mode."), EmbeddedInstance("MSFT_Credential")] String SafeModeAdministratorPassword; [Write, Description("Fully qualified domain name (FQDN) of the parent domain.")] String ParentDomainName; [Write, Description("NetBIOS name for the new domain.")] String DomainNetBiosName; diff --git a/source/DSCResources/MSFT_ADDomain/en-US/about_ADDomain.help.txt b/source/DSCResources/MSFT_ADDomain/en-US/about_ADDomain.help.txt index 8e5242295..acf51880c 100644 --- a/source/DSCResources/MSFT_ADDomain/en-US/about_ADDomain.help.txt +++ b/source/DSCResources/MSFT_ADDomain/en-US/about_ADDomain.help.txt @@ -14,7 +14,7 @@ .PARAMETER Credential Required - PSCredential - Specifies the user name and password that corresponds to the account used to install the domain controller. When adding a child domain these credentials need the correct permission in the parent domain. The credentials will also be used to query for the existence of the domain or child domain. This will not be created as a user in the new domain. The domain administrator password will be the same as the password of the local Administrator of this node. + Specifies the user name and password that corresponds to the account used to install the domain controller. These are only used when adding a child domain and these credentials need the correct permission in the parent domain. This will not be created as a user in the new domain. The domain administrator password will be the same as the password of the local Administrator of this node. .PARAMETER SafeModeAdministratorPassword Required - PSCredential From 32e082769e0b2fa0174175813403119571d4af57 Mon Sep 17 00:00:00 2001 From: Simon Heather Date: Sun, 9 Feb 2020 19:23:05 +0000 Subject: [PATCH 09/13] Add used functions notes. --- .../DSCResources/MSFT_ADDomain/MSFT_ADDomain.psm1 | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.psm1 b/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.psm1 index 0739971bc..ef32b5d47 100644 --- a/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.psm1 +++ b/source/DSCResources/MSFT_ADDomain/MSFT_ADDomain.psm1 @@ -234,6 +234,12 @@ function Get-TargetResource .PARAMETER DomainMode The Domain Functional Level for the entire domain. + + .NOTES + Used Functions: + Name | Module + -------------------|-------------------------- + Resolve-DomainFQDN | ActiveDirectoryDsc.Common #> function Test-TargetResource { @@ -369,13 +375,20 @@ function Test-TargetResource .PARAMETER DomainMode The Domain Functional Level for the entire domain. + + .NOTES + Used Functions: + Name | Module + -------------------------------|-------------------------- + Install-ADDSDomain | ActiveDirectory + Install-ADDSForest | ActiveDirectory #> function Set-TargetResource { <# Suppressing this rule because $global:DSCMachineStatus is used to trigger a reboot for the one that was suppressed when calling - Install-ADDSForest or Install-ADDSDomains. + Install-ADDSForest or Install-ADDSDomain. #> [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '')] [CmdletBinding()] From d3e644abb2c974dd8cec3b0df16f8570efa29746 Mon Sep 17 00:00:00 2001 From: Simon Heather Date: Mon, 10 Feb 2020 08:14:03 +0000 Subject: [PATCH 10/13] Fix unit test mock SysVol value --- Tests/Unit/MSFT_ADDomain.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Unit/MSFT_ADDomain.Tests.ps1 b/Tests/Unit/MSFT_ADDomain.Tests.ps1 index 1b485e092..2f0429ca5 100644 --- a/Tests/Unit/MSFT_ADDomain.Tests.ps1 +++ b/Tests/Unit/MSFT_ADDomain.Tests.ps1 @@ -45,7 +45,7 @@ try $mockParentDomainName = '' $mockDomainFQDN = $mockDomainName $mockNTDSPath = 'C:\Windows\NTDS' - $mockSysVolPath = 'C:\Windows\SYSVOL\sysvol' + $mockSysVolPath = 'C:\Windows\SysVol' $mockDomainSysVolPath = Join-Path -Path $mockSysVolPath -ChildPath $mockDomainName $maxRetries = 15 $forestMode = [Microsoft.DirectoryServices.Deployment.Types.ForestMode]::WinThreshold From 05d7a56d0459150aea7a888e4586884ae3a69463 Mon Sep 17 00:00:00 2001 From: Simon Heather Date: Tue, 11 Feb 2020 11:14:00 +0000 Subject: [PATCH 11/13] Fix mock sysvolpath --- Tests/Unit/MSFT_ADDomain.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Unit/MSFT_ADDomain.Tests.ps1 b/Tests/Unit/MSFT_ADDomain.Tests.ps1 index 2f0429ca5..bbab66414 100644 --- a/Tests/Unit/MSFT_ADDomain.Tests.ps1 +++ b/Tests/Unit/MSFT_ADDomain.Tests.ps1 @@ -174,7 +174,7 @@ try } $mockGetItemPropertyNetlogonResult = @{ - SysVol = $mockSysVolPath + SysVol = $mockSysVolPath + '\sysvol' } Mock -CommandName Get-ItemPropertyValue ` From 571d0b578d949a67cc53d1c74adb453acb8b5673 Mon Sep 17 00:00:00 2001 From: Simon Heather Date: Wed, 12 Feb 2020 08:57:57 +0000 Subject: [PATCH 12/13] Increase markdownlint MD013 line length to 120 --- .markdownlint.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.markdownlint.json b/.markdownlint.json index 87b7da562..86b7082be 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -3,7 +3,9 @@ "MD029": { "style": "one" }, - "MD013": true, + "MD013": { + "line_length": 120 + }, "MD024": false, "MD034": false, "no-hard-tabs": true From a13ce8b59b7d83cbf8fbf04fb03c0a00fb2f414e Mon Sep 17 00:00:00 2001 From: Simon Heather Date: Wed, 12 Feb 2020 08:58:06 +0000 Subject: [PATCH 13/13] Update changelog --- CHANGELOG.md | 109 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 78 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7f4bff91..f7503ac9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,27 +12,52 @@ For older change log history see the [historic changelog](HISTORIC_CHANGELOG.md) - ActiveDirectoryDsc - Added [Codecov.io](https://codecov.io) support. - Fixed miscellaneous spelling errors. +- ADDomain + - Added integration tests + ([issue #302](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/302)). - ADForestProperties - - Added TombstoneLifetime property ([issue #302](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/302)). - - Added Integration tests ([issue #349](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/349)). + - Added TombstoneLifetime property + ([issue #302](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/302)). + - Added Integration tests + ([issue #349](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/349)). ### Fixed - ADForestProperties - - Fixed ability to clear `ServicePrincipalNameSuffix` and `UserPrincipalNameSuffix` ([issue #548](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/548)). + - Fixed ability to clear `ServicePrincipalNameSuffix` and `UserPrincipalNameSuffix` + ([issue #548](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/548)). - ADObjectPermissionEntry - - Fixed issue where Get-DscConfiguration / Test-DscConfiguration throw an exception when target object path does not yet exist ([issue #552](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/552)) - - Fixed issue where Get-TargetResource throw an exception, `Cannot find drive. A drive with the name 'AD' does not exist`, when running soon after domain controller restart ([issue #547](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/547)) + - Fixed issue where Get-DscConfiguration / Test-DscConfiguration throw an exception when target object path does not + yet exist + ([issue #552](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/552)) + - Fixed issue where Get-TargetResource throw an exception, `Cannot find drive. A drive with the name 'AD' does not + exist`, when running soon after domain controller restart + ([issue #547](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/547)) - ADOrganizationalUnit - - Fixed issue where Get-DscConfiguration / Test-DscConfiguration throw an exception when parent path does not yet exist ([issue #553](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/553)) + - Fixed issue where Get-DscConfiguration / Test-DscConfiguration throw an exception when parent path does not yet exist + ([issue #553](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/553)) ### Changed - ActiveDirectoryDsc - - Updated Azure Pipeline Windows image ([issue #551](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/551)). - - Updated license copyright ([issue #550](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/550)). + - BREAKING CHANGE: Required PowerShell version increased from v4.0 to v5.0 + - Updated Azure Pipeline Windows image + ([issue #551](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/551)). + - Updated license copyright + ([issue #550](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/550)). - ADDomain - - Change Domain Install Tracking File to NetLogon Registry Test and Refactor. ([issue #560](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/560)). + - Changed Domain Install Tracking File to use NetLogon Registry Test. + ([issue #560](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/560)). + - Updated the Get-TargetResource function with the following: + - Removed unused parameters. + - Removed unnecessary domain membership check. + - Removed unneeded catch exception blocks. + - Changed Get-ADDomain and Get-ADForest to use localhost as the server. + - Improved Try/Catch blocks to only cover cmdlet calls. + - Simplified retry timing loop. + - Refactored unit tests. + - Updated NewChildDomain example to clarify the contents of the credential parameter and use Windows 2016 rather than + 2012 R2. - ADForestProperties - Refactored unit tests. @@ -41,24 +66,33 @@ For older change log history see the [historic changelog](HISTORIC_CHANGELOG.md) ### Added - ADServicePrincipalName - - Added Integration tests ([issue #358](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/358)). + - Added Integration tests + ([issue #358](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/358)). - ADManagedServiceAccount - Added Integration tests. - ADKDSKey - - Added Integration tests ([issue #351](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/351)). + - Added Integration tests + ([issue #351](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/351)). ### Changed - ADManagedServiceAccount - - KerberosEncryptionType property added. ([issue #511](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/511)). - - BREAKING CHANGE: AccountType parameter ValidateSet changed from ('Group', 'Single') to ('Group', 'Standalone') - Standalone is the correct terminology. Ref: [Service Accounts](https://docs.microsoft.com/en-us/windows/security/identity-protection/access-control/service-accounts). - ([issue #515](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/515)). + - KerberosEncryptionType property added. + ([issue #511](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/511)). + - BREAKING CHANGE: AccountType parameter ValidateSet changed from ('Group', 'Single') to ('Group', 'Standalone') - + Standalone is the correct terminology. + Ref: [Service Accounts](https://docs.microsoft.com/en-us/windows/security/identity-protection/access-control/service-accounts). + ([issue #515](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/515)). - BREAKING CHANGE: AccountType parameter default of Single removed. - Enforce positive choice of account type. - - BREAKING CHANGE: MembershipAttribute parameter ValidateSet member SID changed to ObjectSid to match result property of Get-AdObject. Previous code does not work if SID is specified. + - BREAKING CHANGE: MembershipAttribute parameter ValidateSet member SID changed to ObjectSid to match result property + of Get-AdObject. Previous code does not work if SID is specified. - BREAKING CHANGE: AccountTypeForce parameter removed - unnecessary complication. - - BREAKING CHANGE: Members parameter renamed to ManagedPasswordPrincipals - to closer match Get-AdServiceAccount result property PrincipalsAllowedToRetrieveManagedPassword. This is so that a DelegateToAccountPrincipals parameter can be added later. - - Common Compare-ResourcePropertyState function used to replace function specific Compare-TargetResourceState and code refactored. - ([issue #512](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/512)). + - BREAKING CHANGE: Members parameter renamed to ManagedPasswordPrincipals - to closer match Get-AdServiceAccount result + property PrincipalsAllowedToRetrieveManagedPassword. This is so that a DelegateToAccountPrincipals parameter can be + added later. + - Common Compare-ResourcePropertyState function used to replace function specific Compare-TargetResourceState and code + refactored. + ([issue #512](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/512)). - Resource unit tests refactored to use nested contexts and follow the logic of the module. - ActiveDirectoryDsc - Updated PowerShell help files. @@ -66,40 +100,53 @@ For older change log history see the [historic changelog](HISTORIC_CHANGELOG.md) - Remove verbose parameters from unit tests. - Fix PowerShell script file formatting and culture string alignment. - Add the `pipelineIndentationStyle` setting to the Visual Studio Code settings file. - - Remove unused common function Test-DscParameterState ([issue #522](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/522)). + - Remove unused common function Test-DscParameterState + ([issue #522](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/522)). ### Fixed - ActiveDirectoryDsc - Fix tests ErrorAction on DscResource.Test Import-Module. - ADObjectPermissionEntry - - Updated Assert-ADPSDrive with PSProvider Checks ([issue #527](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/527)). + - Updated Assert-ADPSDrive with PSProvider Checks + ([issue #527](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/527)). - ADReplicationSite - - Fixed incorrect evaluation of site configuration state when no description is defined ([issue #534](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/534)). + - Fixed incorrect evaluation of site configuration state when no description is defined + ([issue #534](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/534)). - ADReplicationSiteLink - - Fix RemovingSites verbose message ([issue #518](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/518)). + - Fix RemovingSites verbose message + ([issue #518](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/518)). - ADComputer - - Fixed the SamAcountName property description ([issue #529](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/529)). + - Fixed the SamAcountName property description + ([issue #529](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/529)). ## 4.2.0.0 ### Added - ADReplicationSite - - Added 'Description' attribute parameter ([issue #500](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/500)). - - Added Integration testing ([issue #355](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/355)). + - Added 'Description' attribute parameter + ([issue #500](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/500)). + - Added Integration testing + ([issue #355](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/355)). - ADReplicationSubnet - - Added 'Description' attribute parameter ([issue #503](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/500)). - - Added Integration testing ([issue #357](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/357)). + - Added 'Description' attribute parameter + ([issue #503](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/500)). + - Added Integration testing + ([issue #357](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/357)). - ADReplicationSiteLink - - Added Integration testing ([issue #356](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/356)). - - Added ability to set 'Options' such as Change Notification Replication ([issue #504](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/504)). + - Added Integration testing + ([issue #356](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/356)). + - Added ability to set 'Options' such as Change Notification Replication + ([issue #504](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/504)). ### Fixed - ActiveDirectoryDsc - Resolved custom Script Analyzer rules that was added to the test framework. - ActiveDirectoryDsc.Common - - Fix `Test-DscPropertyState` Failing when Comparing $Null and Arrays. ([issue #513](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/513)). + - Fix `Test-DscPropertyState` Failing when Comparing $Null and Arrays. + ([issue #513](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/513)). - ADReplicationSite - - Correct value returned for RenameDefaultFirstSiteName ([issue #502](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/502)). + - Correct value returned for RenameDefaultFirstSiteName + ([issue #502](https://github.com/dsccommunity/ActiveDirectoryDsc/issues/502)).