Skip to content

Commit ce6d20c

Browse files
committed
Merge pull request #27 from PlagueHO/Issue-23-Add-ODJ-Support
Added xOfflineDomainJoin resource - Fixes #23
2 parents 9c3e2b1 + 7934ca2 commit ce6d20c

7 files changed

Lines changed: 484 additions & 5 deletions

File tree

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
$moduleRoot = Split-Path `
2+
-Path $MyInvocation.MyCommand.Path `
3+
-Parent
4+
5+
#region LocalizedData
6+
$Culture = 'en-us'
7+
if (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath $PSUICulture))
8+
{
9+
$Culture = $PSUICulture
10+
}
11+
Import-LocalizedData `
12+
-BindingVariable LocalizedData `
13+
-Filename MSFT_xOfflineDomainJoin.psd1 `
14+
-BaseDirectory $moduleRoot `
15+
-UICulture $Culture
16+
#endregion
17+
18+
19+
function Get-TargetResource
20+
{
21+
[CmdletBinding()]
22+
[OutputType([Hashtable])]
23+
param
24+
(
25+
[parameter(Mandatory = $true)]
26+
[ValidateSet('Yes')]
27+
[System.String]
28+
$IsSingleInstance,
29+
30+
[parameter(Mandatory = $true)]
31+
[ValidateNotNullOrEmpty()]
32+
[System.String]
33+
$RequestFile
34+
)
35+
36+
Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): "
37+
$($LocalizedData.GettingOfflineDomainJoinMessage)
38+
) -join '')
39+
40+
# It is not possible to read the ODJ file that was used to join a domain
41+
# So it has to always be returned as blank.
42+
$returnValue = @{
43+
IsSingleInstance = 'Yes'
44+
RequestFile = ''
45+
}
46+
47+
#Output the target resource
48+
$returnValue
49+
} # Get-TargetResource
50+
51+
52+
function Set-TargetResource
53+
{
54+
[CmdletBinding()]
55+
param
56+
(
57+
[parameter(Mandatory = $true)]
58+
[ValidateSet('Yes')]
59+
[System.String]
60+
$IsSingleInstance,
61+
62+
[parameter(Mandatory = $true)]
63+
[ValidateNotNullOrEmpty()]
64+
[System.String]
65+
$RequestFile
66+
)
67+
68+
Write-Verbose -Message ( @( "$($MyInvocation.MyCommand): "
69+
$($LocalizedData.ApplyingOfflineDomainJoinMessage)
70+
) -join '')
71+
72+
# Check the ODJ Request file exists
73+
if (-not (Test-Path -Path $RequestFile))
74+
{
75+
$errorId = 'RequestFileNotFoundError'
76+
$errorCategory = [System.Management.Automation.ErrorCategory]::ObjectNotFound
77+
$errorMessage = $($LocalizedData.RequestFileNotFoundError) `
78+
-f $RequestFile
79+
$exception = New-Object -TypeName System.ArgumentException `
80+
-ArgumentList $errorMessage
81+
$errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord `
82+
-ArgumentList $exception, $errorId, $errorCategory, $null
83+
84+
$PSCmdlet.ThrowTerminatingError($errorRecord)
85+
} # if
86+
87+
# Don't need to check if the domain is already joined because
88+
# Set-TargetResource wouldn't fire unless it wasn't.
89+
Join-Domain -RequestFile $RequestFile
90+
} # Set-TargetResource
91+
92+
93+
function Test-TargetResource
94+
{
95+
[CmdletBinding()]
96+
[OutputType([Boolean])]
97+
param
98+
(
99+
[parameter(Mandatory = $true)]
100+
[ValidateSet('Yes')]
101+
[System.String]
102+
$IsSingleInstance,
103+
104+
[parameter(Mandatory = $true)]
105+
[ValidateNotNullOrEmpty()]
106+
[System.String]
107+
$RequestFile
108+
)
109+
110+
# Flag to signal whether settings are correct
111+
[Boolean] $desiredConfigurationMatch = $true
112+
113+
Write-Verbose -Message ( @("$($MyInvocation.MyCommand): "
114+
$($LocalizedData.CheckingOfflineDomainJoinMessage)
115+
) -join '')
116+
117+
# Check the ODJ Request file exists
118+
if (-not (Test-Path -Path $RequestFile))
119+
{
120+
$errorId = 'RequestFileNotFoundError'
121+
$errorCategory = [System.Management.Automation.ErrorCategory]::ObjectNotFound
122+
$errorMessage = $($LocalizedData.RequestFileNotFoundError) `
123+
-f $RequestFile
124+
$exception = New-Object -TypeName System.ArgumentException `
125+
-ArgumentList $errorMessage
126+
$errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord `
127+
-ArgumentList $exception, $errorId, $errorCategory, $null
128+
129+
$PSCmdlet.ThrowTerminatingError($errorRecord)
130+
} # if
131+
132+
$CurrentDomainName = Get-DomainName
133+
134+
if($CurrentDomainName)
135+
{
136+
# Domain is already joined.
137+
Write-Verbose -Message ( @(
138+
"$($MyInvocation.MyCommand): "
139+
$($LocalizedData.DomainAlreadyJoinedhMessage) `
140+
-f $CurrentDomainName `
141+
) -join '' )
142+
}
143+
else
144+
{
145+
# Domain is not joined, so change is required.
146+
Write-Verbose -Message ( @("$($MyInvocation.MyCommand): "
147+
$($LocalizedData.DomainNotJoinedMessage)
148+
) -join '')
149+
150+
$desiredConfigurationMatch = $false
151+
} # if
152+
return $desiredConfigurationMatch
153+
} # Test-TargetResource
154+
155+
156+
<#
157+
.SYNOPSIS
158+
Uses DJoin.exe to join a Domain using a ODJ Request File.
159+
#>
160+
function Join-Domain {
161+
[CmdletBinding()]
162+
param(
163+
[Parameter(Mandatory=$true)]
164+
[System.String]
165+
$RequestFile
166+
)
167+
168+
Write-Verbose -Message ( @(
169+
"$($MyInvocation.MyCommand): "
170+
$($LocalizedData.AttemptingDomainJoinMessage) `
171+
-f $RequestFile `
172+
) -join '' )
173+
174+
$Result = & djoin.exe @(
175+
'/REQUESTODJ'
176+
'/LOADFILE'
177+
$RequestFile
178+
'/WINDOWSPATH'
179+
$ENV:SystemRoot
180+
'/LOCALOS')
181+
if ($LASTEXITCODE -eq 0)
182+
{
183+
# Notify DSC that a reboot is required.
184+
$global:DSCMachineStatus = 1
185+
}
186+
else
187+
{
188+
Write-Verbose -Message $Result
189+
190+
$errorId = 'DjoinError'
191+
$errorCategory = [System.Management.Automation.ErrorCategory]::ObjectNotFound
192+
$errorMessage = $($LocalizedData.DjoinError) `
193+
-f $LASTEXITCODE
194+
$exception = New-Object -TypeName System.ArgumentException `
195+
-ArgumentList $errorMessage
196+
$errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord `
197+
-ArgumentList $exception, $errorId, $errorCategory, $null
198+
199+
$PSCmdlet.ThrowTerminatingError($errorRecord)
200+
} # if
201+
202+
Write-Verbose -Message ( @(
203+
"$($MyInvocation.MyCommand): "
204+
$($LocalizedData.DomainJoinedMessage) `
205+
-f $RequestFile `
206+
) -join '' )
207+
} # function Join-Domain
208+
209+
210+
<#
211+
.SYNOPSIS
212+
Returns the name of the Domain the computer is joined to or
213+
$null if not domain joined.
214+
#>
215+
function Get-DomainName
216+
{
217+
[CmdletBinding()]
218+
[OutputType([System.String])]
219+
param()
220+
221+
# Use CIM to detect the domain name so that this will work on Nano Server.
222+
$ComputerSystem = Get-CimInstance -ClassName win32_computersystem -Namespace root\cimv2
223+
if ($ComputerSystem.Workgroup)
224+
{
225+
return $null
226+
}
227+
else
228+
{
229+
$ComputerSystem.Domain
230+
}
231+
} # function Get-DomainName
232+
233+
234+
Export-ModuleMember -Function *-TargetResource
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[ClassVersion("1.0.0.0"), FriendlyName("xOfflineDomainJoin")]
2+
class MSFT_xOfflineDomainJoin : OMI_BaseResource
3+
{
4+
[Key, Description("Specifies the resource is a single instance, the value must be 'Yes'"), ValueMap{"Yes"}, Values{"Yes"}] String IsSingleInstance;
5+
[Required, Description("The full path to the Offline Domain Join Request file to use.")] String RequestFile;
6+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
ConvertFrom-StringData @'
2+
GettingOfflineDomainJoinMessage=Getting the Offline Domain Join State.
3+
ApplyingOfflineDomainJoinMessage=Applying the Offline Domain Join State.
4+
AttemptingDomainJoinMessage=Attempting domain join using ODJ Request file '{0}'.
5+
DomainJoinedMessage=Domain joined using ODJ Request file '{0}'. Reboot will be required.
6+
CheckingOfflineDomainJoinMessage=Checking the Offline Domain Join State.
7+
DomainAlreadyJoinedhMessage=The computer is already joined to a domain '{0}'. Change not required.
8+
DomainNotJoinedMessage=The computer is not joined to a domain. Change required.
9+
RequestFileNotFoundError=The ODJ Request file '{0}' does not exist.
10+
DjoinError=Error {0} occured requesting the Offline Domain Join.
11+
'@
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
configuration Sample_xOfflineDomainJoin
2+
{
3+
param
4+
(
5+
[string[]]$NodeName = 'localhost'
6+
)
7+
8+
Import-DSCResource -ModuleName xComputerManagement
9+
10+
Node $NodeName
11+
{
12+
xOfflineDomainJoin ODJ
13+
{
14+
RequestFile = 'C:\ODJ\ODJBlob.txt'
15+
IsSingleInstance = 'Yes'
16+
}
17+
}
18+
}
19+
20+
Sample_xOfflineDomainJoin
21+
Start-DscConfiguration -Path Sample_xOfflineDomainJoin -Wait -Verbose -Force

README.md

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,11 @@ To easily use PowerShell 4.0 on older operating systems, install WMF 4.0.
3232
Please read the installation instructions that are present on both the download page and the release notes for WMF 4.0
3333

3434
## Description
35-
The xComputerManagement module contains the xComputer DSC Resource.
36-
This DSC Resource allows you to configure a computer by changing its name and modifying its domain or workgroup.
35+
The xComputerManagement module contains the following resources:
36+
* xComputer - allows you to configure a computer by changing its name and modifying its domain or workgroup.
37+
* 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.
3738

38-
## Details
39+
## xComputer
3940
xComputer resource has following properties:
4041

4142
* Name: The desired computer name
@@ -45,9 +46,19 @@ xComputer resource has following properties:
4546
* Credential: Credential to be used to join or leave domain
4647
* CurrentOU: A read-only property that specifies the organizational unit that the computer account is currently in
4748

49+
## xOfflineDomainJoin
50+
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:
51+
52+
* IsSingleInstance: Must be set to 'Yes'. Required.
53+
* RequestFile: The full path to the Offline Domain Join request file. Required.
54+
4855
## Versions
4956

5057
### Unreleased
58+
* Added the following resources:
59+
* MSFT_xOfflineDomainJoin resource to join computers to an AD Domain using an Offline Domain Join request file.
60+
* xComputer: Changed credential generation code in tests to avoid triggering PSSA rule PSAvoidUsingConvertToSecureStringWithPlainText.
61+
Renamed unit test file to match the name of Resource file.
5162

5263
### 1.5.0.0
5364
* Update Unit tests to use the standard folder structure and test templates.
@@ -297,5 +308,32 @@ Sample_xComputer_DomainToWorkgroup -ConfigurationData $ConfigData -MachineName <
297308
****************************#>
298309
```
299310

311+
### Join a Domain using an ODJ Request File
312+
This example will join the computer to a domain using the ODJ request file C:\ODJ\ODJRequest.txt.
313+
314+
```powershell
315+
configuration Sample_xOfflineDomainJoin
316+
{
317+
param
318+
(
319+
[string[]]$NodeName = 'localhost'
320+
)
321+
322+
Import-DSCResource -ModuleName xComputerManagement
323+
324+
Node $NodeName
325+
{
326+
xOfflineDomainJoin ODJ
327+
{
328+
RequestFile = 'C:\ODJ\ODJRequest.txt'
329+
IsSingleInstance = 'Yes'
330+
}
331+
}
332+
}
333+
334+
Sample_xOfflineDomainJoin
335+
Start-DscConfiguration -Path Sample_xOfflineDomainJoin -Wait -Verbose -Force
336+
```
337+
300338
## Contributing
301339
Please check out common DSC Resources [contributing guidelines](https://github.com/PowerShell/DscResource.Kit/blob/master/CONTRIBUTING.md).
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ try
2727
InModuleScope $Global:DSCResourceName {
2828

2929
Describe $Global:DSCResourceName {
30-
31-
$SecPassword = ConvertTo-SecureString -String 'password' -AsPlainText -Force
30+
# A real password isn't needed here - use this next line to avoid triggering PSSA rule
31+
$SecPassword = New-Object -Type SecureString
3232
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'USER',$SecPassword
3333
$NotComputerName = if($env:COMPUTERNAME -ne 'othername'){'othername'}else{'name'}
3434

0 commit comments

Comments
 (0)