From 15f3d01f11f295699084b6056717d125ebae985f Mon Sep 17 00:00:00 2001 From: Simon Heather Date: Tue, 11 Jun 2019 20:58:10 +0100 Subject: [PATCH 1/5] Fix and tests for Issue #221 --- DSCResources/MSFT_Computer/MSFT_Computer.psm1 | 21 ++- .../en-US/MSFT_Computer.strings.psd1 | 1 + Tests/Unit/MSFT_Computer.Tests.ps1 | 133 +++++++++++++++++- 3 files changed, 153 insertions(+), 2 deletions(-) diff --git a/DSCResources/MSFT_Computer/MSFT_Computer.psm1 b/DSCResources/MSFT_Computer/MSFT_Computer.psm1 index e1818e6b..5d9e02b1 100644 --- a/DSCResources/MSFT_Computer/MSFT_Computer.psm1 +++ b/DSCResources/MSFT_Computer/MSFT_Computer.psm1 @@ -251,7 +251,26 @@ function Set-TargetResource } # Rename the computer, and join it to the domain. - Add-Computer @addComputerParameters + try + { + Add-Computer @addComputerParameters + } + catch [System.InvalidOperationException] + { + if ($_.Exception -like '*The directory service is busy*') + { + Write-Verbose -Message $script:localizedData.DirectoryBusyMessage + Rename-Computer -NewName $Name -DomainCredential $Credential + } + else + { + New-InvalidOperationException -ErrorRecord $_ + } + } + catch + { + New-InvalidOperationException -ErrorRecord $_ + } 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..5e6c0e26 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}'. + DirectoryBusyMessage = Directory was busy during domain join, trying rename again. 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..ea386e39 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,106 @@ try Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $WorkGroupName } } + It 'Should Try a separate rename if the domain is busy' { + Mock -CommandName Get-WMIObject -MockWith { + [PSCustomObject] @{ + Domain = 'Contoso'; + Workgroup = 'Contoso'; + PartOfDomain = $false + } + } + + Mock -CommandName Get-ComputerDomain -MockWith { + '' + } + + Mock -CommandName Add-Computer -MockWith { + Throw [System.InvalidOperationException]::new('The directory service is busy') + } + + 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 +793,8 @@ try '' } + Mock -CommandName Add-Computer + Set-TargetResource ` -Name $notComputerName ` -WorkGroupName 'adventure-works' ` @@ -701,6 +818,8 @@ try 'contoso.com' } + Mock -CommandName Add-Computer + Set-TargetResource ` -Name $env:COMPUTERNAME ` -DomainName 'adventure-works.com' ` @@ -727,6 +846,8 @@ try 'contoso.com' } + Mock -CommandName Add-Computer + Set-TargetResource ` -Name 'localhost' ` -DomainName 'adventure-works.com' ` @@ -753,6 +874,8 @@ try 'contoso.com' } + Mock -CommandName Add-Computer + Set-TargetResource ` -Name $env:COMPUTERNAME ` -DomainName 'adventure-works.com' ` @@ -780,6 +903,8 @@ try 'contoso.com' } + Mock -CommandName Add-Computer + Set-TargetResource ` -Name 'localhost' ` -DomainName 'adventure-works.com' ` @@ -807,6 +932,8 @@ try '' } + Mock -CommandName Add-Computer + Set-TargetResource ` -Name $env:COMPUTERNAME ` -WorkGroupName 'Contoso' ` @@ -832,6 +959,8 @@ try '' } + Mock -CommandName Add-Computer + Set-TargetResource ` -Name 'localhost' ` -WorkGroupName 'Contoso' ` @@ -857,6 +986,8 @@ try 'contoso.com' } + Mock -CommandName Add-Computer + Set-TargetResource ` -Name $notComputerName ` -Credential $credential ` From 12929fd70234cc35259422a92a7705c01e2d618e Mon Sep 17 00:00:00 2001 From: Simon Heather Date: Tue, 11 Jun 2019 21:19:57 +0100 Subject: [PATCH 2/5] Updated CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 416fae4c..ea6abf0e 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: From f96b00c6eb0ec45b4786cd9dcafddb749f9e60fa Mon Sep 17 00:00:00 2001 From: Simon Heather Date: Thu, 20 Jun 2019 13:12:28 +0100 Subject: [PATCH 3/5] Review updates --- CHANGELOG.md | 4 +-- DSCResources/MSFT_Computer/MSFT_Computer.psm1 | 8 ++++-- .../en-US/MSFT_Computer.strings.psd1 | 2 +- Tests/Unit/MSFT_Computer.Tests.ps1 | 28 +++++++++++-------- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea6abf0e..fed6392e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,8 @@ ## 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) + - 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 diff --git a/DSCResources/MSFT_Computer/MSFT_Computer.psm1 b/DSCResources/MSFT_Computer/MSFT_Computer.psm1 index 5d9e02b1..b55f6151 100644 --- a/DSCResources/MSFT_Computer/MSFT_Computer.psm1 +++ b/DSCResources/MSFT_Computer/MSFT_Computer.psm1 @@ -251,15 +251,17 @@ function Set-TargetResource } # Rename the computer, and join it to the domain. + $script:FailToRenameAfterJoinDomainErrorId = 'FailToRenameAfterJoinDomain,Microsoft.PowerShell.Commands.AddComputerCommand' try { Add-Computer @addComputerParameters } catch [System.InvalidOperationException] { - if ($_.Exception -like '*The directory service is busy*') + # If the rename failed during the domain join, re-try the rename + if ($_.FullyQualifiedErrorId -eq $script:FailToRenameAfterJoinDomainErrorId) { - Write-Verbose -Message $script:localizedData.DirectoryBusyMessage + Write-Verbose -Message $script:localizedData.FailToRenameAfterJoinDomainMessage Rename-Computer -NewName $Name -DomainCredential $Credential } else @@ -269,7 +271,7 @@ function Set-TargetResource } catch { - New-InvalidOperationException -ErrorRecord $_ + 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 5e6c0e26..011bdec9 100644 --- a/DSCResources/MSFT_Computer/en-US/MSFT_Computer.strings.psd1 +++ b/DSCResources/MSFT_Computer/en-US/MSFT_Computer.strings.psd1 @@ -5,7 +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}'. - DirectoryBusyMessage = Directory was busy during domain join, trying rename again. + 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 ea386e39..0262d631 100644 --- a/Tests/Unit/MSFT_Computer.Tests.ps1 +++ b/Tests/Unit/MSFT_Computer.Tests.ps1 @@ -680,11 +680,17 @@ try Assert-MockCalled -CommandName Add-Computer -Exactly -Times 0 -Scope It -ParameterFilter { $WorkGroupName } } - It 'Should Try a separate rename if the domain is busy' { + 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 = 'FailToRenameAfterJoinDomain,Microsoft.PowerShell.Commands.AddComputerCommand' + $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'; + Domain = 'Contoso' + Workgroup = 'Contoso' PartOfDomain = $false } } @@ -694,7 +700,7 @@ try } Mock -CommandName Add-Computer -MockWith { - Throw [System.InvalidOperationException]::new('The directory service is busy') + Throw $errorRecord } Set-TargetResource ` @@ -706,17 +712,16 @@ try 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'; + Domain = 'Contoso' + Workgroup = 'Contoso' PartOfDomain = $false } } @@ -743,15 +748,14 @@ try 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'; + Domain = 'Contoso' + Workgroup = 'Contoso' PartOfDomain = $false } } From 071bf9b5901b55580437862a719a17bb9e333b46 Mon Sep 17 00:00:00 2001 From: Simon Heather Date: Sun, 23 Jun 2019 11:20:04 +0100 Subject: [PATCH 4/5] Review changes --- CHANGELOG.md | 4 ++-- DSCResources/MSFT_Computer/MSFT_Computer.psm1 | 7 +++++-- Tests/Unit/MSFT_Computer.Tests.ps1 | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fed6392e..8ed15d55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,8 @@ ## 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). + - 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 diff --git a/DSCResources/MSFT_Computer/MSFT_Computer.psm1 b/DSCResources/MSFT_Computer/MSFT_Computer.psm1 index b55f6151..d2953e0a 100644 --- a/DSCResources/MSFT_Computer/MSFT_Computer.psm1 +++ b/DSCResources/MSFT_Computer/MSFT_Computer.psm1 @@ -258,8 +258,11 @@ function Set-TargetResource } catch [System.InvalidOperationException] { - # If the rename failed during the domain join, re-try the rename - if ($_.FullyQualifiedErrorId -eq $script:FailToRenameAfterJoinDomainErrorId) + # 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 diff --git a/Tests/Unit/MSFT_Computer.Tests.ps1 b/Tests/Unit/MSFT_Computer.Tests.ps1 index 0262d631..706c0e05 100644 --- a/Tests/Unit/MSFT_Computer.Tests.ps1 +++ b/Tests/Unit/MSFT_Computer.Tests.ps1 @@ -683,7 +683,7 @@ try 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 = 'FailToRenameAfterJoinDomain,Microsoft.PowerShell.Commands.AddComputerCommand' + $errorID = $failToRenameAfterJoinDomainErrorId $errorCategory = [Management.Automation.ErrorCategory]::InvalidOperation $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, $errorID, $errorCategory, $null) From 1955672177eefdf8ad30f76ed79d3ceb512da6df Mon Sep 17 00:00:00 2001 From: Simon Heather Date: Mon, 24 Jun 2019 14:36:40 +0100 Subject: [PATCH 5/5] Review updates --- DSCResources/MSFT_Computer/MSFT_Computer.psm1 | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/DSCResources/MSFT_Computer/MSFT_Computer.psm1 b/DSCResources/MSFT_Computer/MSFT_Computer.psm1 index d2953e0a..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,17 +253,18 @@ function Set-TargetResource } # Rename the computer, and join it to the domain. - $script:FailToRenameAfterJoinDomainErrorId = 'FailToRenameAfterJoinDomain,Microsoft.PowerShell.Commands.AddComputerCommand' 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 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