diff --git a/CHANGELOG.md b/CHANGELOG.md index 416fae4c..8ed15d55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +- xComputer: + - Fix for 'directory service is busy' error when joining a domain and renaming + a computer when JoinOU is specified - Fixes [Issue #221](https://github.com/PowerShell/ComputerManagementDsc/issues/221). + ## 6.4.0.0 - ScheduledTask: diff --git a/DSCResources/MSFT_Computer/MSFT_Computer.psm1 b/DSCResources/MSFT_Computer/MSFT_Computer.psm1 index e1818e6b..036be220 100644 --- a/DSCResources/MSFT_Computer/MSFT_Computer.psm1 +++ b/DSCResources/MSFT_Computer/MSFT_Computer.psm1 @@ -20,6 +20,8 @@ $script:localizedData = Get-LocalizedData ` -ResourceName 'MSFT_Computer' ` -ResourcePath (Split-Path -Parent $Script:MyInvocation.MyCommand.Path) +$FailToRenameAfterJoinDomainErrorId = 'FailToRenameAfterJoinDomain,Microsoft.PowerShell.Commands.AddComputerCommand' + <# .SYNOPSIS Gets the current state of the computer. @@ -251,7 +253,32 @@ function Set-TargetResource } # Rename the computer, and join it to the domain. - Add-Computer @addComputerParameters + try + { + Add-Computer @addComputerParameters + } + catch [System.InvalidOperationException] + { + <# + If the rename failed during the domain join, re-try the rename. + References to this issue: + https://social.technet.microsoft.com/Forums/windowsserver/en-US/81105b18-b1ff-4fcc-ae5c-2c1a7cf7bf3d/addcomputer-to-domain-with-new-name-returns-error + https://powershell.org/forums/topic/the-directory-service-is-busy/ + #> + if ($_.FullyQualifiedErrorId -eq $failToRenameAfterJoinDomainErrorId) + { + Write-Verbose -Message $script:localizedData.FailToRenameAfterJoinDomainMessage + Rename-Computer -NewName $Name -DomainCredential $Credential + } + else + { + New-InvalidOperationException -ErrorRecord $_ + } + } + catch + { + throw $_ + } if ($rename) { diff --git a/DSCResources/MSFT_Computer/en-US/MSFT_Computer.strings.psd1 b/DSCResources/MSFT_Computer/en-US/MSFT_Computer.strings.psd1 index 7652bb17..011bdec9 100644 --- a/DSCResources/MSFT_Computer/en-US/MSFT_Computer.strings.psd1 +++ b/DSCResources/MSFT_Computer/en-US/MSFT_Computer.strings.psd1 @@ -5,6 +5,7 @@ ConvertFrom-StringData @' RenamedComputerMessage = Renamed computer to '{0}'. RenamedComputerAndJoinedDomainMessage = Renamed computer to '{0}' and added to the domain '{1}'. JoinedDomainMessage = Added computer to domain '{0}'. + FailToRenameAfterJoinDomainMessage = Failed to rename the computer during the domain join. Re-trying the rename. RenamedComputerAndJoinedWorkgroupMessage = Renamed computer to '{0}' and addded to workgroup '{1}'. JoinedWorkgroupMessage = Added computer to workgroup '{0}'. CredentialsNotSpecifiedError = Must to specify credentials with domain. diff --git a/Tests/Unit/MSFT_Computer.Tests.ps1 b/Tests/Unit/MSFT_Computer.Tests.ps1 index 1db05a0e..706c0e05 100644 --- a/Tests/Unit/MSFT_Computer.Tests.ps1 +++ b/Tests/Unit/MSFT_Computer.Tests.ps1 @@ -481,13 +481,14 @@ try Context "$($script:dscResourceName)\Set-TargetResource" { Mock -CommandName Rename-Computer - Mock -CommandName Add-Computer Mock -CommandName Set-CimInstance It 'Throws if both DomainName and WorkGroupName are specified' { $errorRecord = Get-InvalidOperationRecord ` -Message ($LocalizedData.DomainNameAndWorkgroupNameError) + Mock -CommandName Add-Computer + { Set-TargetResource ` -Name $env:COMPUTERNAME ` @@ -505,6 +506,8 @@ try -Message ($LocalizedData.CredentialsNotSpecifiedError) ` -ArgumentName 'Credentials' + Mock -CommandName Add-Computer + { Set-TargetResource ` -Name $env:COMPUTERNAME ` @@ -529,6 +532,8 @@ try 'contoso.com' } + Mock -CommandName Add-Computer + Set-TargetResource ` -Name $notComputerName ` -DomainName 'adventure-works.com' ` @@ -554,6 +559,8 @@ try 'contoso.com' } + Mock -CommandName Add-Computer + Set-TargetResource ` -Name $notComputerName ` -DomainName 'adventure-works.com' ` @@ -580,6 +587,8 @@ try 'contoso.com' } + Mock -CommandName Add-Computer + Set-TargetResource ` -Name $notComputerName ` -WorkGroupName 'contoso' ` @@ -604,6 +613,8 @@ try '' } + Mock -CommandName Add-Computer + Set-TargetResource ` -Name $notComputerName ` -DomainName 'Contoso.com' ` @@ -628,6 +639,8 @@ try '' } + Mock -CommandName Add-Computer + Set-TargetResource ` -Name $notComputerName ` -DomainName 'Contoso.com' ` @@ -653,6 +666,8 @@ try '' } + Mock -CommandName Add-Computer + Set-TargetResource ` -Name $notComputerName ` -DomainName 'Contoso.com' ` @@ -665,6 +680,110 @@ try Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $WorkGroupName } } + It 'Should try a separate rename if ''FailToRenameAfterJoinDomain'' occured during domain join' { + $message = "Computer '' was successfully joined to the new domain '', but renaming it to '' failed with the following error message: The directory service is busy." + $exception = [System.InvalidOperationException]::new($message) + $errorID = $failToRenameAfterJoinDomainErrorId + $errorCategory = [Management.Automation.ErrorCategory]::InvalidOperation + $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, $errorID, $errorCategory, $null) + + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso' + Workgroup = 'Contoso' + PartOfDomain = $false + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + '' + } + + Mock -CommandName Add-Computer -MockWith { + Throw $errorRecord + } + + Set-TargetResource ` + -Name $notComputerName ` + -DomainName 'Contoso.com' ` + -JoinOU 'OU=Computers,DC=contoso,DC=com' ` + -Credential $credential | Should -BeNullOrEmpty + + Assert-MockCalled -CommandName Rename-Computer -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 1 -Scope It -ParameterFilter { $DomainName -and $NewName } + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $WorkGroupName } + } + + It 'Should Throw the correct error if Add-Computer errors with an unknown InvalidOperationException' { + $error = 'Unknown Error' + $errorRecord = [System.InvalidOperationException]::new($error) + + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso' + Workgroup = 'Contoso' + PartOfDomain = $false + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + '' + } + + Mock -CommandName Add-Computer -MockWith { + Throw $errorRecord + } + + Mock -CommandName New-InvalidOperationException -MockWith { + Throw $errorRecord + } + + { Set-TargetResource ` + -Name $notComputerName ` + -DomainName 'Contoso.com' ` + -JoinOU 'OU=Computers,DC=contoso,DC=com' ` + -Credential $credential + } | Should -Throw $error + + Assert-MockCalled -CommandName Rename-Computer -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 1 -Scope It -ParameterFilter { $DomainName -and $NewName } + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $WorkGroupName } + } + + It 'Should Throw the correct error if Add-Computer errors with an unknown error' { + $errorRecord = 'Unknown Error' + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso' + Workgroup = 'Contoso' + PartOfDomain = $false + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + '' + } + + Mock -CommandName Add-Computer -MockWith { + Throw $errorRecord + } + + Mock -CommandName New-InvalidOperationException -MockWith { + Throw $errorRecord + } + + { Set-TargetResource ` + -Name $notComputerName ` + -DomainName 'Contoso.com' ` + -JoinOU 'OU=Computers,DC=contoso,DC=com' ` + -Credential $credential + } | Should -Throw $errorRecord + + Assert-MockCalled -CommandName Rename-Computer -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 1 -Scope It -ParameterFilter { $DomainName -and $NewName } + Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $WorkGroupName } + } + It 'Changes ComputerName and changes Workgroup to new Workgroup' { Mock -CommandName Get-WMIObject -MockWith { [PSCustomObject] @{ @@ -678,6 +797,8 @@ try '' } + Mock -CommandName Add-Computer + Set-TargetResource ` -Name $notComputerName ` -WorkGroupName 'adventure-works' ` @@ -701,6 +822,8 @@ try 'contoso.com' } + Mock -CommandName Add-Computer + Set-TargetResource ` -Name $env:COMPUTERNAME ` -DomainName 'adventure-works.com' ` @@ -727,6 +850,8 @@ try 'contoso.com' } + Mock -CommandName Add-Computer + Set-TargetResource ` -Name 'localhost' ` -DomainName 'adventure-works.com' ` @@ -753,6 +878,8 @@ try 'contoso.com' } + Mock -CommandName Add-Computer + Set-TargetResource ` -Name $env:COMPUTERNAME ` -DomainName 'adventure-works.com' ` @@ -780,6 +907,8 @@ try 'contoso.com' } + Mock -CommandName Add-Computer + Set-TargetResource ` -Name 'localhost' ` -DomainName 'adventure-works.com' ` @@ -807,6 +936,8 @@ try '' } + Mock -CommandName Add-Computer + Set-TargetResource ` -Name $env:COMPUTERNAME ` -WorkGroupName 'Contoso' ` @@ -832,6 +963,8 @@ try '' } + Mock -CommandName Add-Computer + Set-TargetResource ` -Name 'localhost' ` -WorkGroupName 'Contoso' ` @@ -857,6 +990,8 @@ try 'contoso.com' } + Mock -CommandName Add-Computer + Set-TargetResource ` -Name $notComputerName ` -Credential $credential `