From 42400dc05311716e496ee4471c2dd8784a0a626b Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Wed, 6 Apr 2016 11:53:59 +1200 Subject: [PATCH 1/8] Added xOfflineDomainJoin resource --- README.md | 43 +++- Tests/Unit/xComputermanagement.Tests.ps1 | 249 ----------------------- 2 files changed, 40 insertions(+), 252 deletions(-) delete mode 100644 Tests/Unit/xComputermanagement.Tests.ps1 diff --git a/README.md b/README.md index a5654e4f..381e74db 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,11 @@ To easily use PowerShell 4.0 on older operating systems, install WMF 4.0. Please read the installation instructions that are present on both the download page and the release notes for WMF 4.0 ## Description -The xComputerManagement module contains the xComputer DSC Resource. -This DSC Resource allows you to configure a computer by changing its name and modifying its domain or workgroup. +The xComputerManagement module contains the following resources: +* xComputer - allows you to configure a computer by changing its name and modifying its domain or workgroup. +* xOfflineDomainJoin - allows you to join computers to an AD Domain using an [Offline Domain Join](https://technet.microsoft.com/en-us/library/offline-domain-join-djoin-step-by-step(v=ws.10).aspx) request file. -## Details +## xComputer xComputer resource has following properties: * Name: The desired computer name @@ -45,9 +46,18 @@ xComputer resource has following properties: * Credential: Credential to be used to join or leave domain * CurrentOU: A read-only property that specifies the organizational unit that the computer account is currently in +## xOfflineDomainJoin +xOfflineDomainJoin resource is a [Single Instance](https://msdn.microsoft.com/en-us/powershell/dsc/singleinstance) resource that can only be used once in a configuration and has following properties: + +* IsSingleInstance: Must be set to 'Yes'. Required. +* RequestFile: The full path to the Offline Domain Join request file. Required. + ## Versions ### Unreleased +* Added the following resources: + * MSFT_xOfflineDomainJoin resource to join computers to an AD Domain using an Offline Domain Join request file. +* xComputer: Changed credential generation code in tests to avoid triggering PSSA rule PSAvoidUsingConvertToSecureStringWithPlainText. ### 1.5.0.0 * Update Unit tests to use the standard folder structure and test templates. @@ -297,5 +307,32 @@ Sample_xComputer_DomainToWorkgroup -ConfigurationData $ConfigData -MachineName < ****************************#> ``` +### Join a Domain using an ODJ Request File +This example will join the computer to a domain using the ODJ request file C:\ODJ\ODJRequest.txt. + +```powershell +configuration Sample_xOfflineDomainJoin +{ + param + ( + [string[]]$NodeName = 'localhost' + ) + + Import-DSCResource -ModuleName xComputerManagement + + Node $NodeName + { + xOfflineDomainJoin ODJ + { + RequestFile = 'C:\ODJ\ODJRequest.txt' + IsSingleInstance = 'Yes' + } + } +} + +Sample_xOfflineDomainJoin +Start-DscConfiguration -Path Sample_xOfflineDomainJoin -Wait -Verbose -Force +``` + ## Contributing Please check out common DSC Resources [contributing guidelines](https://github.com/PowerShell/DscResource.Kit/blob/master/CONTRIBUTING.md). diff --git a/Tests/Unit/xComputermanagement.Tests.ps1 b/Tests/Unit/xComputermanagement.Tests.ps1 deleted file mode 100644 index be2a8e28..00000000 --- a/Tests/Unit/xComputermanagement.Tests.ps1 +++ /dev/null @@ -1,249 +0,0 @@ -$Global:DSCModuleName = 'xComputerManagement' -$Global:DSCResourceName = 'MSFT_xComputer' - -#region HEADER -[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path)) -if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) -{ - & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\')) -} -else -{ - & git @('-C',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\'),'pull') -} -Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force -$TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $Global:DSCModuleName ` - -DSCResourceName $Global:DSCResourceName ` - -TestType Unit -#endregion - -# Begin Testing -try -{ - #region Pester Tests - - InModuleScope $Global:DSCResourceName { - - Describe $Global:DSCResourceName { - - $SecPassword = ConvertTo-SecureString -String 'password' -AsPlainText -Force - $Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'USER',$SecPassword - $NotComputerName = if($env:COMPUTERNAME -ne 'othername'){'othername'}else{'name'} - - Context "$($Global:DSCResourceName)\Test-TargetResource" { - Mock Get-WMIObject {[PSCustomObject]@{DomainName = 'ContosoLtd'}} -ParameterFilter {$Class -eq 'Win32_NTDomain'} - It 'Throws if both DomainName and WorkGroupName are specified' { - {Test-TargetResource -Name $Env:ComputerName -DomainName 'contoso.com' -WorkGroupName 'workgroup'} | Should Throw - } - It 'Throws if Domain is specified without Credentials' { - {Test-TargetResource -Name $Env:ComputerName -DomainName 'contoso.com'} | Should Throw - } - It 'Should return True if Domain name is same as specified' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock GetComputerDomain {'contoso.com'} - Test-TargetResource -Name $Env:ComputerName -DomainName 'Contoso.com' -Credential $Credential | Should Be $true - } - It 'Should return True if Workgroup name is same as specified' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Workgroup';Workgroup='Workgroup';PartOfDomain=$false}} - Mock GetComputerDomain {''} - Test-TargetResource -Name $Env:ComputerName -WorkGroupName 'workgroup' | Should Be $true - } - It 'Should return True if ComputerName and Domain name is same as specified' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock GetComputerDomain {'contoso.com'} - Test-TargetResource -Name $Env:ComputerName -DomainName 'contoso.com' -Credential $Credential | Should Be $true - } - It 'Should return True if ComputerName and Workgroup is same as specified' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Workgroup';Workgroup='Workgroup';PartOfDomain=$false}} - Mock GetComputerDomain {''} - Test-TargetResource -Name $Env:ComputerName -WorkGroupName 'workgroup' | Should Be $true - } - It 'Should return True if ComputerName is same and no Domain or Workgroup specified' { - Mock Get-WmiObject {[PSCustomObject]@{Domain = 'Workgroup';Workgroup='Workgroup';PartOfDomain=$false}} - Mock GetComputerDomain {''} - Test-TargetResource -Name $Env:ComputerName | Should Be $true - Mock Get-WmiObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock GetComputerDomain {'contoso.com'} - Test-TargetResource -Name $Env:ComputerName | Should Be $true - } - It 'Should return False if ComputerName is not same and no Domain or Workgroup specified' { - Mock Get-WmiObject {[PSCustomObject]@{Domain = 'Workgroup';Workgroup='Workgroup';PartOfDomain=$false}} - Mock GetComputerDomain {''} - Test-TargetResource -Name $NotComputerName | Should Be $false - Mock Get-WmiObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock GetComputerDomain {'contoso.com'} - Test-TargetResource -Name $NotComputerName | Should Be $false - } - It 'Should return False if Domain name is not same as specified' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock GetComputerDomain {'contoso.com'} - Test-TargetResource -Name $Env:ComputerName -DomainName 'adventure-works.com' -Credential $Credential | Should Be $false - } - It 'Should return False if Workgroup name is not same as specified' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Workgroup';Workgroup='Workgroup';PartOfDomain=$false}} - Mock GetComputerDomain {''} - Test-TargetResource -Name $Env:ComputerName -WorkGroupName 'NOTworkgroup' | Should Be $false - } - It 'Should return False if ComputerName is not same as specified' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Workgroup';Workgroup='Workgroup';PartOfDomain=$false}} - Mock GetComputerDomain {''} - Test-TargetResource -Name $NotComputerName -WorkGroupName 'workgroup' | Should Be $false - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock GetComputerDomain {'contoso.com'} - Test-TargetResource -Name $NotComputerName -DomainName 'contoso.com' -Credential $Credential | Should Be $false - } - It 'Should return False if Computer is in Workgroup and Domain is specified' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$false}} - Mock GetComputerDomain {''} - Test-TargetResource -Name $Env:ComputerName -DomainName 'contoso.com' -Credential $Credential | Should Be $false - } - It 'Should return False if ComputerName is in Domain and Workgroup is specified' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock GetComputerDomain {'contoso.com'} - Test-TargetResource -Name $Env:ComputerName -WorkGroupName 'Contoso' -Credential $Credential -UnjoinCredential $Credential | Should Be $false - } - It 'Throws if name is to long' { - {Test-TargetResource -Name "ThisNameIsTooLong"} | Should Throw - } - It 'Throws if name contains illigal characters' { - {Test-TargetResource -Name "ThisIsBad<>"} | Should Throw - } - - } - Context "$($Global:DSCResourceName)\Get-TargetResource" { - It 'should not throw' { - {Get-TargetResource -Name $env:COMPUTERNAME} | Should Not Throw - } - It 'Should return a hashtable containing Name, DomainName, JoinOU, CurrentOU, Credential, UnjoinCredential and WorkGroupName' { - $Result = Get-TargetResource -Name $env:COMPUTERNAME - $Result.GetType().Fullname | Should Be 'System.Collections.Hashtable' - $Result.Keys | Should Be @('Name', 'DomainName', 'JoinOU', 'CurrentOU', 'Credential', 'UnjoinCredential', 'WorkGroupName') - } - It 'Throws if name is to long' { - {Get-TargetResource -Name "ThisNameIsTooLong"} | Should Throw - } - It 'Throws if name contains illigal characters' { - {Get-TargetResource -Name "ThisIsBad<>"} | Should Throw - } - } - Context "$($Global:DSCResourceName)\Set-TargetResource" { - Mock Rename-Computer {} - Mock Add-Computer {} - It 'Throws if both DomainName and WorkGroupName are specified' { - {Set-TargetResource -Name $Env:ComputerName -DomainName 'contoso.com' -WorkGroupName 'workgroup'} | Should Throw - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It - } - It 'Throws if Domain is specified without Credentials' { - {Set-TargetResource -Name $Env:ComputerName -DomainName 'contoso.com'} | Should Throw - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It - } - It 'Changes ComputerName and changes Domain to new Domain' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock GetComputerDomain {'contoso.com'} - Set-TargetResource -Name $NotComputerName -DomainName 'adventure-works.com' -Credential $Credential -UnjoinCredential $Credential | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$DomainName -and $NewName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$WorkGroupName} - } - It 'Changes ComputerName and changes Domain to new Domain with specified OU' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock GetComputerDomain {'contoso.com'} - Set-TargetResource -Name $NotComputerName -DomainName 'adventure-works.com' -JoinOU 'OU=Computers,DC=contoso,DC=com' -Credential $Credential -UnjoinCredential $Credential | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$DomainName -and $NewName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$WorkGroupName} - } - It 'Changes ComputerName and changes Domain to Workgroup' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock GetComputerDomain {'contoso.com'} - Set-TargetResource -Name $NotComputerName -WorkGroupName 'contoso' -Credential $Credential | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$WorkGroupName -and $NewName -and $Credential} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$DomainName -or $UnjoinCredential} - } - It 'Changes ComputerName and changes Workgroup to Domain' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso';Workgroup='Contoso';PartOfDomain=$false}} - Mock GetComputerDomain {''} - Set-TargetResource -Name $NotComputerName -DomainName 'Contoso.com' -Credential $Credential | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$DomainName -and $NewName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$WorkGroupName} - } - It 'Changes ComputerName and changes Workgroup to Domain with specified OU' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso';Workgroup='Contoso';PartOfDomain=$false}} - Mock GetComputerDomain {''} - Set-TargetResource -Name $NotComputerName -DomainName 'Contoso.com' -JoinOU 'OU=Computers,DC=contoso,DC=com' -Credential $Credential | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$DomainName -and $NewName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$WorkGroupName} - } - It 'Changes ComputerName and changes Workgroup to new Workgroup' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso';Workgroup='Contoso';PartOfDomain=$false}} - Mock GetComputerDomain {''} - Set-TargetResource -Name $NotComputerName -WorkGroupName 'adventure-works' | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$WorkGroupName -and $NewName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$DomainName} - } - It 'Changes only the Domain to new Domain' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock GetComputerDomain {'contoso.com'} - Set-TargetResource -Name $Env:ComputerName -DomainName 'adventure-works.com' -Credential $Credential -UnjoinCredential $Credential | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$DomainName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$NewName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$WorkGroupName} - } - It 'Changes only the Domain to new Domain with specified OU' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock GetComputerDomain {'contoso.com'} - Set-TargetResource -Name $Env:ComputerName -DomainName 'adventure-works.com' -JoinOU 'OU=Computers,DC=contoso,DC=com' -Credential $Credential -UnjoinCredential $Credential | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$DomainName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$NewName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$WorkGroupName} - } - It 'Changes only Domain to Workgroup' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock GetComputerDomain {''} - Set-TargetResource -Name $Env:ComputerName -WorkGroupName 'Contoso' -UnjoinCredential $Credential | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$NewName} - Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$WorkGroupName} - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$DomainName} - } - It 'Changes only ComputerName in Domain' { - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} - Mock GetComputerDomain {'contoso.com'} - Set-TargetResource -Name $NotComputerName -Credential $Credential | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 1 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It - } - It 'Changes only ComputerName in Workgroup' { - Mock GetComputerDomain {''} - Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso';Workgroup='Contoso';PartOfDomain=$false}} - Set-TargetResource -Name $NotComputerName | Should BeNullOrEmpty - Assert-MockCalled -CommandName Rename-Computer -Exactly 1 -Scope It - Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It - } - It 'Throws if name is to long' { - {Set-TargetResource -Name "ThisNameIsTooLong"} | Should Throw - } - It 'Throws if name contains illigal characters' { - {Set-TargetResource -Name "ThisIsBad<>"} | Should Throw - } - } - } - } #end InModuleScope $DSCResourceName - #endregion -} -finally -{ - #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment - #endregion -} From f678a6b8ea784f1841d3314c7e28128d7ff01e94 Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Wed, 6 Apr 2016 11:56:12 +1200 Subject: [PATCH 2/8] Added xOfflineDomainJoin resource --- .../MSFT_xOfflineDomainJoin.psm1 | 208 +++++++++++++++ .../MSFT_xOfflineDomainJoin.schema.mof | 6 + .../en-us/MSFT_xOfflineDomainJoin.psd1 | 10 + README.md | 1 + Tests/Unit/MSFT_xComputermanagement.Tests.ps1 | 249 ++++++++++++++++++ Tests/Unit/MSFT_xOfflineDomainJoin.tests.ps1 | 136 ++++++++++ 6 files changed, 610 insertions(+) create mode 100644 DSCResources/MSFT_xOfflineDomainJoin/MSFT_xOfflineDomainJoin.psm1 create mode 100644 DSCResources/MSFT_xOfflineDomainJoin/MSFT_xOfflineDomainJoin.schema.mof create mode 100644 DSCResources/MSFT_xOfflineDomainJoin/en-us/MSFT_xOfflineDomainJoin.psd1 create mode 100644 Tests/Unit/MSFT_xComputermanagement.Tests.ps1 create mode 100644 Tests/Unit/MSFT_xOfflineDomainJoin.tests.ps1 diff --git a/DSCResources/MSFT_xOfflineDomainJoin/MSFT_xOfflineDomainJoin.psm1 b/DSCResources/MSFT_xOfflineDomainJoin/MSFT_xOfflineDomainJoin.psm1 new file mode 100644 index 00000000..edfe5b1d --- /dev/null +++ b/DSCResources/MSFT_xOfflineDomainJoin/MSFT_xOfflineDomainJoin.psm1 @@ -0,0 +1,208 @@ +$moduleRoot = Split-Path ` + -Path $MyInvocation.MyCommand.Path ` + -Parent + +#region LocalizedData +$Culture = 'en-us' +if (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath $PSUICulture)) +{ + $Culture = $PSUICulture +} +Import-LocalizedData ` + -BindingVariable LocalizedData ` + -Filename MSFT_xOfflineDomainJoin.psd1 ` + -BaseDirectory $moduleRoot ` + -UICulture $Culture +#endregion + + +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([Hashtable])] + param + ( + [parameter(Mandatory = $true)] + [ValidateSet('Yes')] + [System.String] + $IsSingleInstance, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $RequestFile + ) + + Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " + $($LocalizedData.GettingOfflineDomainJoinMessage) + ) -join '') + + # It is not possible to read the ODJ file that was used to join a domain + # So it has to always be returned as blank. + $returnValue = @{ + IsSingleInstance = 'Yes' + RequestFile = '' + } + + #Output the target resource + $returnValue +} # Get-TargetResource + + +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [parameter(Mandatory = $true)] + [ValidateSet('Yes')] + [System.String] + $IsSingleInstance, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $RequestFile + ) + + Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " + $($LocalizedData.ApplyingOfflineDomainJoinMessage) + ) -join '') + + # Check the ODJ Request file exists + if (-not (Test-Path -Path $RequestFile)) + { + $errorId = 'RequestFileNotFoundError' + $errorCategory = [System.Management.Automation.ErrorCategory]::ObjectNotFound + $errorMessage = $($LocalizedData.RequestFileNotFoundError) ` + -f $RequestFile + $exception = New-Object -TypeName System.ArgumentException ` + -ArgumentList $errorMessage + $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $exception, $errorId, $errorCategory, $null + + $PSCmdlet.ThrowTerminatingError($errorRecord) + } # if + + # Don't need to check if the domain is already joined because + # Set-TargetResource wouldn't fire unless it wasn't. + Join-Domain -RequestFile $RequestFile +} # Set-TargetResource + + +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([Boolean])] + param + ( + [parameter(Mandatory = $true)] + [ValidateSet('Yes')] + [System.String] + $IsSingleInstance, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $RequestFile + ) + + # Flag to signal whether settings are correct + [Boolean] $desiredConfigurationMatch = $true + + Write-Verbose -Message ( @("$($MyInvocation.MyCommand): " + $($LocalizedData.CheckingOfflineDomainJoinMessage) + ) -join '') + + # Check the ODJ Request file exists + if (-not (Test-Path -Path $RequestFile)) + { + $errorId = 'RequestFileNotFoundError' + $errorCategory = [System.Management.Automation.ErrorCategory]::ObjectNotFound + $errorMessage = $($LocalizedData.RequestFileNotFoundError) ` + -f $RequestFile + $exception = New-Object -TypeName System.ArgumentException ` + -ArgumentList $errorMessage + $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $exception, $errorId, $errorCategory, $null + + $PSCmdlet.ThrowTerminatingError($errorRecord) + } # if + + $CurrentDomainName = Get-DomainName + + if($CurrentDomainName) + { + # Domain is already joined. + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($LocalizedData.DomainAlreadyJoinedhMessage) ` + -f $CurrentDomainName ` + ) -join '' ) + } + else + { + # Domain is not joined, so change is required. + Write-Verbose -Message ( @("$($MyInvocation.MyCommand): " + $($LocalizedData.DomainNotJoinedMessage) + ) -join '') + + $desiredConfigurationMatch = $false + } # if + return $desiredConfigurationMatch +} # Test-TargetResource + + +<# +.SYNOPSIS +Uses DJoin.exe to join a Domain using a ODJ Request File. +#> +function Join-Domain { + [CmdletBinding()] + param( + [Parameter(Mandatory=$true)] + [System.String] + $RequestFile + ) + + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($LocalizedData.AttemptingDomainJoinMessage) ` + -f $RequestFile ` + ) -join '' ) + + & djoin.exe /REQUESTODJ /LOADFILE $RequestFile + + Write-Verbose -Message ( @( + "$($MyInvocation.MyCommand): " + $($LocalizedData.DomainJoinedMessage) ` + -f $RequestFile ` + ) -join '' ) +} # function Join-Domain + + +<# +.SYNOPSIS +Returns the name of the Domain the computer is joined to or +$null if not domain joined. +#> +function Get-DomainName +{ + [CmdletBinding()] + [OutputType([System.String])] + param() + + # Use CIM to detect the domain name so that this will work on Nano Server. + $ComputerSystem = Get-CimInstance -ClassName win32_computersystem -Namespace root\cimv2 + if ($ComputerSystem.Workgroup) + { + return $null + } + else + { + $ComputerSystem.Domain + } +} # function Get-DomainName + + +Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xOfflineDomainJoin/MSFT_xOfflineDomainJoin.schema.mof b/DSCResources/MSFT_xOfflineDomainJoin/MSFT_xOfflineDomainJoin.schema.mof new file mode 100644 index 00000000..2ffc8b06 --- /dev/null +++ b/DSCResources/MSFT_xOfflineDomainJoin/MSFT_xOfflineDomainJoin.schema.mof @@ -0,0 +1,6 @@ +[ClassVersion("1.0.0.0"), FriendlyName("xOfflineDomainJoin")] +class MSFT_xOfflineDomainJoin : OMI_BaseResource +{ + [Key, Description("Specifies the resource is a single instance, the value must be 'Yes'"), ValueMap{"Yes"}, Values{"Yes"}] String IsSingleInstance; + [Required, Description("The full path to the Offline Domain Join Request file to use.")] String RequestFile; +}; diff --git a/DSCResources/MSFT_xOfflineDomainJoin/en-us/MSFT_xOfflineDomainJoin.psd1 b/DSCResources/MSFT_xOfflineDomainJoin/en-us/MSFT_xOfflineDomainJoin.psd1 new file mode 100644 index 00000000..98e32c84 --- /dev/null +++ b/DSCResources/MSFT_xOfflineDomainJoin/en-us/MSFT_xOfflineDomainJoin.psd1 @@ -0,0 +1,10 @@ +ConvertFrom-StringData @' + GettingOfflineDomainJoinMessage=Getting the Offline Domain Join State. + ApplyingOfflineDomainJoinMessage=Applying the Offline Domain Join State. + AttemptingDomainJoinMessage=Attempting domain join using ODJ Request file '{0}'. + DomainJoinedMessage=Domain joined using ODJ Request file '{0}'. Reboot will be required. + CheckingOfflineDomainJoinMessage=Checking the Offline Domain Join State. + DomainAlreadyJoinedhMessage=The computer is already joined to a domain '{0}'. Change not required. + DomainNotJoinedMessage=The computer is not joined to a domain. Change required. + RequestFileNotFoundError=The ODJ Request file '{0}' does not exist. +'@ diff --git a/README.md b/README.md index 381e74db..388bfc40 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ xOfflineDomainJoin resource is a [Single Instance](https://msdn.microsoft.com/en * Added the following resources: * MSFT_xOfflineDomainJoin resource to join computers to an AD Domain using an Offline Domain Join request file. * xComputer: Changed credential generation code in tests to avoid triggering PSSA rule PSAvoidUsingConvertToSecureStringWithPlainText. + Renamed unit test file to match the name of Resource file. ### 1.5.0.0 * Update Unit tests to use the standard folder structure and test templates. diff --git a/Tests/Unit/MSFT_xComputermanagement.Tests.ps1 b/Tests/Unit/MSFT_xComputermanagement.Tests.ps1 new file mode 100644 index 00000000..b0870a24 --- /dev/null +++ b/Tests/Unit/MSFT_xComputermanagement.Tests.ps1 @@ -0,0 +1,249 @@ +$Global:DSCModuleName = 'xComputerManagement' +$Global:DSCResourceName = 'MSFT_xComputer' + +#region HEADER +[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path)) +if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\')) +} +else +{ + & git @('-C',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\'),'pull') +} +Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $Global:DSCModuleName ` + -DSCResourceName $Global:DSCResourceName ` + -TestType Unit +#endregion + +# Begin Testing +try +{ + #region Pester Tests + + InModuleScope $Global:DSCResourceName { + + Describe $Global:DSCResourceName { + # A real password isn't needed here - use this next line to avoid triggering PSSA rule + $SecPassword = New-Object -Type SecureString + $Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'USER',$SecPassword + $NotComputerName = if($env:COMPUTERNAME -ne 'othername'){'othername'}else{'name'} + + Context "$($Global:DSCResourceName)\Test-TargetResource" { + Mock Get-WMIObject {[PSCustomObject]@{DomainName = 'ContosoLtd'}} -ParameterFilter {$Class -eq 'Win32_NTDomain'} + It 'Throws if both DomainName and WorkGroupName are specified' { + {Test-TargetResource -Name $Env:ComputerName -DomainName 'contoso.com' -WorkGroupName 'workgroup'} | Should Throw + } + It 'Throws if Domain is specified without Credentials' { + {Test-TargetResource -Name $Env:ComputerName -DomainName 'contoso.com'} | Should Throw + } + It 'Should return True if Domain name is same as specified' { + Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} + Mock GetComputerDomain {'contoso.com'} + Test-TargetResource -Name $Env:ComputerName -DomainName 'Contoso.com' -Credential $Credential | Should Be $true + } + It 'Should return True if Workgroup name is same as specified' { + Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Workgroup';Workgroup='Workgroup';PartOfDomain=$false}} + Mock GetComputerDomain {''} + Test-TargetResource -Name $Env:ComputerName -WorkGroupName 'workgroup' | Should Be $true + } + It 'Should return True if ComputerName and Domain name is same as specified' { + Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} + Mock GetComputerDomain {'contoso.com'} + Test-TargetResource -Name $Env:ComputerName -DomainName 'contoso.com' -Credential $Credential | Should Be $true + } + It 'Should return True if ComputerName and Workgroup is same as specified' { + Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Workgroup';Workgroup='Workgroup';PartOfDomain=$false}} + Mock GetComputerDomain {''} + Test-TargetResource -Name $Env:ComputerName -WorkGroupName 'workgroup' | Should Be $true + } + It 'Should return True if ComputerName is same and no Domain or Workgroup specified' { + Mock Get-WmiObject {[PSCustomObject]@{Domain = 'Workgroup';Workgroup='Workgroup';PartOfDomain=$false}} + Mock GetComputerDomain {''} + Test-TargetResource -Name $Env:ComputerName | Should Be $true + Mock Get-WmiObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} + Mock GetComputerDomain {'contoso.com'} + Test-TargetResource -Name $Env:ComputerName | Should Be $true + } + It 'Should return False if ComputerName is not same and no Domain or Workgroup specified' { + Mock Get-WmiObject {[PSCustomObject]@{Domain = 'Workgroup';Workgroup='Workgroup';PartOfDomain=$false}} + Mock GetComputerDomain {''} + Test-TargetResource -Name $NotComputerName | Should Be $false + Mock Get-WmiObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} + Mock GetComputerDomain {'contoso.com'} + Test-TargetResource -Name $NotComputerName | Should Be $false + } + It 'Should return False if Domain name is not same as specified' { + Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} + Mock GetComputerDomain {'contoso.com'} + Test-TargetResource -Name $Env:ComputerName -DomainName 'adventure-works.com' -Credential $Credential | Should Be $false + } + It 'Should return False if Workgroup name is not same as specified' { + Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Workgroup';Workgroup='Workgroup';PartOfDomain=$false}} + Mock GetComputerDomain {''} + Test-TargetResource -Name $Env:ComputerName -WorkGroupName 'NOTworkgroup' | Should Be $false + } + It 'Should return False if ComputerName is not same as specified' { + Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Workgroup';Workgroup='Workgroup';PartOfDomain=$false}} + Mock GetComputerDomain {''} + Test-TargetResource -Name $NotComputerName -WorkGroupName 'workgroup' | Should Be $false + Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} + Mock GetComputerDomain {'contoso.com'} + Test-TargetResource -Name $NotComputerName -DomainName 'contoso.com' -Credential $Credential | Should Be $false + } + It 'Should return False if Computer is in Workgroup and Domain is specified' { + Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$false}} + Mock GetComputerDomain {''} + Test-TargetResource -Name $Env:ComputerName -DomainName 'contoso.com' -Credential $Credential | Should Be $false + } + It 'Should return False if ComputerName is in Domain and Workgroup is specified' { + Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} + Mock GetComputerDomain {'contoso.com'} + Test-TargetResource -Name $Env:ComputerName -WorkGroupName 'Contoso' -Credential $Credential -UnjoinCredential $Credential | Should Be $false + } + It 'Throws if name is to long' { + {Test-TargetResource -Name "ThisNameIsTooLong"} | Should Throw + } + It 'Throws if name contains illigal characters' { + {Test-TargetResource -Name "ThisIsBad<>"} | Should Throw + } + + } + Context "$($Global:DSCResourceName)\Get-TargetResource" { + It 'should not throw' { + {Get-TargetResource -Name $env:COMPUTERNAME} | Should Not Throw + } + It 'Should return a hashtable containing Name, DomainName, JoinOU, CurrentOU, Credential, UnjoinCredential and WorkGroupName' { + $Result = Get-TargetResource -Name $env:COMPUTERNAME + $Result.GetType().Fullname | Should Be 'System.Collections.Hashtable' + $Result.Keys | Should Be @('Name', 'DomainName', 'JoinOU', 'CurrentOU', 'Credential', 'UnjoinCredential', 'WorkGroupName') + } + It 'Throws if name is to long' { + {Get-TargetResource -Name "ThisNameIsTooLong"} | Should Throw + } + It 'Throws if name contains illigal characters' { + {Get-TargetResource -Name "ThisIsBad<>"} | Should Throw + } + } + Context "$($Global:DSCResourceName)\Set-TargetResource" { + Mock Rename-Computer {} + Mock Add-Computer {} + It 'Throws if both DomainName and WorkGroupName are specified' { + {Set-TargetResource -Name $Env:ComputerName -DomainName 'contoso.com' -WorkGroupName 'workgroup'} | Should Throw + Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It + } + It 'Throws if Domain is specified without Credentials' { + {Set-TargetResource -Name $Env:ComputerName -DomainName 'contoso.com'} | Should Throw + Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It + } + It 'Changes ComputerName and changes Domain to new Domain' { + Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} + Mock GetComputerDomain {'contoso.com'} + Set-TargetResource -Name $NotComputerName -DomainName 'adventure-works.com' -Credential $Credential -UnjoinCredential $Credential | Should BeNullOrEmpty + Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$DomainName -and $NewName} + Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$WorkGroupName} + } + It 'Changes ComputerName and changes Domain to new Domain with specified OU' { + Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} + Mock GetComputerDomain {'contoso.com'} + Set-TargetResource -Name $NotComputerName -DomainName 'adventure-works.com' -JoinOU 'OU=Computers,DC=contoso,DC=com' -Credential $Credential -UnjoinCredential $Credential | Should BeNullOrEmpty + Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$DomainName -and $NewName} + Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$WorkGroupName} + } + It 'Changes ComputerName and changes Domain to Workgroup' { + Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} + Mock GetComputerDomain {'contoso.com'} + Set-TargetResource -Name $NotComputerName -WorkGroupName 'contoso' -Credential $Credential | Should BeNullOrEmpty + Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$WorkGroupName -and $NewName -and $Credential} + Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$DomainName -or $UnjoinCredential} + } + It 'Changes ComputerName and changes Workgroup to Domain' { + Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso';Workgroup='Contoso';PartOfDomain=$false}} + Mock GetComputerDomain {''} + Set-TargetResource -Name $NotComputerName -DomainName 'Contoso.com' -Credential $Credential | Should BeNullOrEmpty + Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$DomainName -and $NewName} + Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$WorkGroupName} + } + It 'Changes ComputerName and changes Workgroup to Domain with specified OU' { + Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso';Workgroup='Contoso';PartOfDomain=$false}} + Mock GetComputerDomain {''} + Set-TargetResource -Name $NotComputerName -DomainName 'Contoso.com' -JoinOU 'OU=Computers,DC=contoso,DC=com' -Credential $Credential | Should BeNullOrEmpty + Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$DomainName -and $NewName} + Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$WorkGroupName} + } + It 'Changes ComputerName and changes Workgroup to new Workgroup' { + Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso';Workgroup='Contoso';PartOfDomain=$false}} + Mock GetComputerDomain {''} + Set-TargetResource -Name $NotComputerName -WorkGroupName 'adventure-works' | Should BeNullOrEmpty + Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$WorkGroupName -and $NewName} + Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$DomainName} + } + It 'Changes only the Domain to new Domain' { + Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} + Mock GetComputerDomain {'contoso.com'} + Set-TargetResource -Name $Env:ComputerName -DomainName 'adventure-works.com' -Credential $Credential -UnjoinCredential $Credential | Should BeNullOrEmpty + Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$DomainName} + Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$NewName} + Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$WorkGroupName} + } + It 'Changes only the Domain to new Domain with specified OU' { + Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} + Mock GetComputerDomain {'contoso.com'} + Set-TargetResource -Name $Env:ComputerName -DomainName 'adventure-works.com' -JoinOU 'OU=Computers,DC=contoso,DC=com' -Credential $Credential -UnjoinCredential $Credential | Should BeNullOrEmpty + Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$DomainName} + Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$NewName} + Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$WorkGroupName} + } + It 'Changes only Domain to Workgroup' { + Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} + Mock GetComputerDomain {''} + Set-TargetResource -Name $Env:ComputerName -WorkGroupName 'Contoso' -UnjoinCredential $Credential | Should BeNullOrEmpty + Assert-MockCalled -CommandName Rename-Computer -Exactly 0 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$NewName} + Assert-MockCalled -CommandName Add-Computer -Exactly 1 -Scope It -ParameterFilter {$WorkGroupName} + Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It -ParameterFilter {$DomainName} + } + It 'Changes only ComputerName in Domain' { + Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso.com';Workgroup='Contoso.com';PartOfDomain=$true}} + Mock GetComputerDomain {'contoso.com'} + Set-TargetResource -Name $NotComputerName -Credential $Credential | Should BeNullOrEmpty + Assert-MockCalled -CommandName Rename-Computer -Exactly 1 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It + } + It 'Changes only ComputerName in Workgroup' { + Mock GetComputerDomain {''} + Mock Get-WMIObject {[PSCustomObject]@{Domain = 'Contoso';Workgroup='Contoso';PartOfDomain=$false}} + Set-TargetResource -Name $NotComputerName | Should BeNullOrEmpty + Assert-MockCalled -CommandName Rename-Computer -Exactly 1 -Scope It + Assert-MockCalled -CommandName Add-Computer -Exactly 0 -Scope It + } + It 'Throws if name is to long' { + {Set-TargetResource -Name "ThisNameIsTooLong"} | Should Throw + } + It 'Throws if name contains illigal characters' { + {Set-TargetResource -Name "ThisIsBad<>"} | Should Throw + } + } + } + } #end InModuleScope $DSCResourceName + #endregion +} +finally +{ + #region FOOTER + Restore-TestEnvironment -TestEnvironment $TestEnvironment + #endregion +} diff --git a/Tests/Unit/MSFT_xOfflineDomainJoin.tests.ps1 b/Tests/Unit/MSFT_xOfflineDomainJoin.tests.ps1 new file mode 100644 index 00000000..b0ba317c --- /dev/null +++ b/Tests/Unit/MSFT_xOfflineDomainJoin.tests.ps1 @@ -0,0 +1,136 @@ +$Global:DSCModuleName = 'xComputerManagement' +$Global:DSCResourceName = 'MSFT_xOfflineDomainJoin' + +#region HEADER +[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path)) +if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\')) +} +else +{ + & git @('-C',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\'),'pull') +} +Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $Global:DSCModuleName ` + -DSCResourceName $Global:DSCResourceName ` + -TestType Unit +#endregion + +# Begin Testing +try +{ + #region Pester Tests + + InModuleScope $Global:DSCResourceName { + + $TestOfflineDomainJoin = @{ + IsSingleInstance = 'Yes' + RequestFile = 'C:\ODJRequest.txt' + } + + Describe "$($Global:DSCResourceName)\Get-TargetResource" { + + It 'should return the correct values' { + $Result = Get-TargetResource ` + @TestOfflineDomainJoin + + $Result.IsSingleInstance | Should Be $TestOfflineDomainJoin.IsSingleInstance + $Result.RequestFile | Should Be '' + } + } + + Describe "$($Global:DSCResourceName)\Set-TargetResource" { + Mock Test-Path -MockWith { return $True } + Mock Join-Domain + + Context 'Domain is not joined' { + It 'should not throw exception' { + { Set-TargetResource @TestOfflineDomainJoin } | Should Not Throw + } + It 'Should do call all the mocks' { + Assert-MockCalled Test-Path -Times 1 + Assert-MockCalled Join-Domain -Times 1 + } + } + + Mock Test-Path -MockWith { return $False } + + Context 'ODJ Request file is not found' { + It 'should throw RequestFileNotFoundError exception' { + $errorId = 'RequestFileNotFoundError' + $errorCategory = [System.Management.Automation.ErrorCategory]::ObjectNotFound + $errorMessage = $($LocalizedData.RequestFileNotFoundError) ` + -f $TestOfflineDomainJoin.RequestFile + $exception = New-Object -TypeName System.ArgumentException ` + -ArgumentList $errorMessage + $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $exception, $errorId, $errorCategory, $null + + { Test-TargetResource @TestOfflineDomainJoin } | Should Throw $errorRecord + } + It 'should do call all the mocks' { + Assert-MockCalled Test-Path -Times 1 + Assert-MockCalled Join-Domain -Times 0 + } + } + } + + Describe "$($Global:DSCResourceName)\Test-TargetResource" { + Mock Test-Path -MockWith { return $True } + Mock Get-DomainName -MockWith { return $null } + + Context 'Domain is not joined' { + It 'should return false' { + Test-TargetResource @TestOfflineDomainJoin | should be $false + } + It 'Should do call all the mocks' { + Assert-MockCalled Test-Path -Times 1 + Assert-MockCalled Get-DomainName -Times 1 + } + } + + Mock Get-DomainName -MockWith { return 'contoso.com' } + + Context 'Domain is already joined' { + It 'should return false' { + Test-TargetResource @TestOfflineDomainJoin | should be $true + } + It 'Should do call all the mocks' { + Assert-MockCalled Test-Path -Times 1 + Assert-MockCalled Get-DomainName -Times 1 + } + } + + Mock Test-Path -MockWith { return $False } + + Context 'ODJ Request file is not found' { + It 'should throw RequestFileNotFoundError exception' { + $errorId = 'RequestFileNotFoundError' + $errorCategory = [System.Management.Automation.ErrorCategory]::ObjectNotFound + $errorMessage = $($LocalizedData.RequestFileNotFoundError) ` + -f $TestOfflineDomainJoin.RequestFile + $exception = New-Object -TypeName System.ArgumentException ` + -ArgumentList $errorMessage + $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $exception, $errorId, $errorCategory, $null + + { Test-TargetResource @TestOfflineDomainJoin } | Should Throw $errorRecord + } + It 'Should do call all the mocks' { + Assert-MockCalled Test-Path -Times 1 + Assert-MockCalled Get-DomainName -Times 0 + } + } + } + } #end InModuleScope $DSCResourceName + #endregion +} +finally +{ + #region FOOTER + Restore-TestEnvironment -TestEnvironment $TestEnvironment + #endregion +} From 4cf45588250be0b2f4c28dac1821e0f0435d9b06 Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Thu, 7 Apr 2016 12:23:00 +1200 Subject: [PATCH 3/8] Moved Examples Folder --- Examples/Sample_xOfflineDomainJoin.ps1 | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Examples/Sample_xOfflineDomainJoin.ps1 diff --git a/Examples/Sample_xOfflineDomainJoin.ps1 b/Examples/Sample_xOfflineDomainJoin.ps1 new file mode 100644 index 00000000..16df00ca --- /dev/null +++ b/Examples/Sample_xOfflineDomainJoin.ps1 @@ -0,0 +1,21 @@ +configuration Sample_xOfflineDomainJoin +{ + param + ( + [string[]]$NodeName = 'localhost' + ) + + Import-DSCResource -ModuleName xComputerManagement + + Node $NodeName + { + xOfflineDomainJoin ODJ + { + RequestFile = 'C:\ODJ\ODJBlob.txt' + IsSingleInstance = 'Yes' + } + } +} + +Sample_xOfflineDomainJoin +Start-DscConfiguration -Path Sample_xOfflineDomainJoin -Wait -Verbose -Force From 9b8bc42aa4b6e0ef60854bad55dd8ed6fa9b39ca Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Thu, 7 Apr 2016 18:44:50 +1200 Subject: [PATCH 4/8] Added code to ensure DSC reboots after DJOIN --- .../MSFT_xOfflineDomainJoin/MSFT_xOfflineDomainJoin.psm1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/DSCResources/MSFT_xOfflineDomainJoin/MSFT_xOfflineDomainJoin.psm1 b/DSCResources/MSFT_xOfflineDomainJoin/MSFT_xOfflineDomainJoin.psm1 index edfe5b1d..68746320 100644 --- a/DSCResources/MSFT_xOfflineDomainJoin/MSFT_xOfflineDomainJoin.psm1 +++ b/DSCResources/MSFT_xOfflineDomainJoin/MSFT_xOfflineDomainJoin.psm1 @@ -172,6 +172,9 @@ function Join-Domain { ) -join '' ) & djoin.exe /REQUESTODJ /LOADFILE $RequestFile + + # Notify DSC that a reboot is required. + $global:DSCMachineStatus = 1 Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " From 6d0539e12a10dfb43c4ead86784b0657b43e67cf Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Thu, 7 Apr 2016 20:05:14 +1200 Subject: [PATCH 5/8] Fix DJOIN Command --- .../MSFT_xOfflineDomainJoin/MSFT_xOfflineDomainJoin.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DSCResources/MSFT_xOfflineDomainJoin/MSFT_xOfflineDomainJoin.psm1 b/DSCResources/MSFT_xOfflineDomainJoin/MSFT_xOfflineDomainJoin.psm1 index 68746320..d14ac262 100644 --- a/DSCResources/MSFT_xOfflineDomainJoin/MSFT_xOfflineDomainJoin.psm1 +++ b/DSCResources/MSFT_xOfflineDomainJoin/MSFT_xOfflineDomainJoin.psm1 @@ -171,7 +171,7 @@ function Join-Domain { -f $RequestFile ` ) -join '' ) - & djoin.exe /REQUESTODJ /LOADFILE $RequestFile + & djoin.exe @('/REQUESTODJ','/LOADFILE',$RequestFile,'/WINDOWSPATH',$ENV:SystemRoot) # Notify DSC that a reboot is required. $global:DSCMachineStatus = 1 From 96ce46f2ecaa09882e369200d940f4fb8d751802 Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Thu, 7 Apr 2016 20:50:23 +1200 Subject: [PATCH 6/8] Added check for Djoin result --- .../MSFT_xOfflineDomainJoin.psm1 | 31 ++++++++++++++++--- .../en-us/MSFT_xOfflineDomainJoin.psd1 | 1 + 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/DSCResources/MSFT_xOfflineDomainJoin/MSFT_xOfflineDomainJoin.psm1 b/DSCResources/MSFT_xOfflineDomainJoin/MSFT_xOfflineDomainJoin.psm1 index d14ac262..186878b5 100644 --- a/DSCResources/MSFT_xOfflineDomainJoin/MSFT_xOfflineDomainJoin.psm1 +++ b/DSCResources/MSFT_xOfflineDomainJoin/MSFT_xOfflineDomainJoin.psm1 @@ -171,10 +171,33 @@ function Join-Domain { -f $RequestFile ` ) -join '' ) - & djoin.exe @('/REQUESTODJ','/LOADFILE',$RequestFile,'/WINDOWSPATH',$ENV:SystemRoot) - - # Notify DSC that a reboot is required. - $global:DSCMachineStatus = 1 + $Result = & djoin.exe @( + '/REQUESTODJ' + '/LOADFILE' + $RequestFile + '/WINDOWSPATH' + $ENV:SystemRoot + '/LOCALOS') + if ($LASTEXITCODE -eq 0) + { + # Notify DSC that a reboot is required. + $global:DSCMachineStatus = 1 + } + else + { + Write-Verbose -Message $Result + + $errorId = 'DjoinError' + $errorCategory = [System.Management.Automation.ErrorCategory]::ObjectNotFound + $errorMessage = $($LocalizedData.DjoinError) ` + -f $LASTEXITCODE + $exception = New-Object -TypeName System.ArgumentException ` + -ArgumentList $errorMessage + $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $exception, $errorId, $errorCategory, $null + + $PSCmdlet.ThrowTerminatingError($errorRecord) + } # if Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): " diff --git a/DSCResources/MSFT_xOfflineDomainJoin/en-us/MSFT_xOfflineDomainJoin.psd1 b/DSCResources/MSFT_xOfflineDomainJoin/en-us/MSFT_xOfflineDomainJoin.psd1 index 98e32c84..18b5b961 100644 --- a/DSCResources/MSFT_xOfflineDomainJoin/en-us/MSFT_xOfflineDomainJoin.psd1 +++ b/DSCResources/MSFT_xOfflineDomainJoin/en-us/MSFT_xOfflineDomainJoin.psd1 @@ -7,4 +7,5 @@ ConvertFrom-StringData @' DomainAlreadyJoinedhMessage=The computer is already joined to a domain '{0}'. Change not required. DomainNotJoinedMessage=The computer is not joined to a domain. Change required. RequestFileNotFoundError=The ODJ Request file '{0}' does not exist. + DjoinError=Error {0} occured requesting the Offline Domain Join. '@ From e168854faddd25da723d4a3389f8ef4da81eb89d Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Thu, 7 Apr 2016 21:00:18 +1200 Subject: [PATCH 7/8] Added Join-Domain unit tests --- Tests/Unit/MSFT_xOfflineDomainJoin.tests.ps1 | 33 ++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Tests/Unit/MSFT_xOfflineDomainJoin.tests.ps1 b/Tests/Unit/MSFT_xOfflineDomainJoin.tests.ps1 index b0ba317c..1413925e 100644 --- a/Tests/Unit/MSFT_xOfflineDomainJoin.tests.ps1 +++ b/Tests/Unit/MSFT_xOfflineDomainJoin.tests.ps1 @@ -125,6 +125,39 @@ try } } } + + Describe "$($Global:DSCResourceName)\Join-Domain" { + Mock djoin.exe -MockWith { $Global:LASTEXITCODE = 0; return "OK" } + + Context 'Domain Join successful' { + It 'should not throw' { + { Join-Domain -RequestFile 'c:\doesnotmatter.txt' } | Should Not Throw + } + It 'Should do call all the mocks' { + Assert-MockCalled djoin.exe -Times 1 + } + } + + Mock djoin.exe -MockWith { $Global:LASTEXITCODE = 99; return "ERROR" } + + Context 'Domain Join successful' { + $errorId = 'DjoinError' + $errorCategory = [System.Management.Automation.ErrorCategory]::ObjectNotFound + $errorMessage = $($LocalizedData.DjoinError) ` + -f 99 + $exception = New-Object -TypeName System.ArgumentException ` + -ArgumentList $errorMessage + $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $exception, $errorId, $errorCategory, $null + + It 'should not throw' { + { Join-Domain -RequestFile 'c:\doesnotmatter.txt' } | Should Throw $errorRecord + } + It 'Should do call all the mocks' { + Assert-MockCalled djoin.exe -Times 1 + } + } + } } #end InModuleScope $DSCResourceName #endregion } From 7934ca2768d451464ef76d4d52c33ebab7b22078 Mon Sep 17 00:00:00 2001 From: Daniel Scott-Raynsford Date: Fri, 8 Apr 2016 22:04:11 +1200 Subject: [PATCH 8/8] Correct filename of xComputer Unit tests to match Resource --- ...SFT_xComputermanagement.Tests.ps1 => MSFT_xComputer.Tests.ps1} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Tests/Unit/{MSFT_xComputermanagement.Tests.ps1 => MSFT_xComputer.Tests.ps1} (100%) diff --git a/Tests/Unit/MSFT_xComputermanagement.Tests.ps1 b/Tests/Unit/MSFT_xComputer.Tests.ps1 similarity index 100% rename from Tests/Unit/MSFT_xComputermanagement.Tests.ps1 rename to Tests/Unit/MSFT_xComputer.Tests.ps1