Providers/AD/Public/Connect-ADEnvironment.ps1
|
function Connect-ADEnvironment { <# .SYNOPSIS Connects to an Active Directory domain, importing RSAT only now .DESCRIPTION There is nothing to authenticate here. Active Directory is reached with the caller's own Windows identity, so "connecting" means proving the four things every later call assumes and recording the answer: - The ActiveDirectory and GroupPolicy modules can be imported. - The session is elevated, because creating OUs and users needs it. - A domain can be reached and named. - The provider's seed data is on disk. Doing that once, here, is the point. Without it the first failure surfaces halfway through seeding, after some objects exist and some do not, and the message names a missing cmdlet rather than a missing feature. **The RSAT import happens here rather than in the manifest.** TestEnvironment declares no RequiredModules, and a contract test enforces it, because the providers do not share a platform: Entra and Okta reach a REST API from any host. Declaring ActiveDirectory in the manifest would make importing the module fail on a Linux container for somebody who only wanted to seed an Okta org. So the dependency is loaded at the moment it is genuinely needed, and its absence is reported as a sentence rather than a binding error. .PARAMETER Server Domain controller to work against. Defaults to whichever the machine would choose. .PARAMETER Credential Alternate credentials. Rarely wanted: the RSAT cmdlets use the caller's identity, and this is recorded so functions that accept -Credential can pass it on. .PARAMETER InstallRsat Install the RSAT AD and Group Policy features if they are missing. Requires elevation and is a machine-wide change, so it is opt-in rather than automatic. .PARAMETER PassThru Returns the connection summary .OUTPUTS ADEnvironmentConnection when -PassThru is supplied .EXAMPLE PS> Connect-TestEnvironment -Provider AD DESCRIPTION: Verifies prerequisites and fixes the session on the local domain OUTPUT: Nothing, unless -PassThru is supplied USE CASE: The normal path on a domain-joined workstation or a domain controller .EXAMPLE PS> Connect-TestEnvironment -Provider AD -Server dc01.ad.contoso.com -PassThru DESCRIPTION: Pins the session to one domain controller OUTPUT: The connection, naming the domain and the controller USE CASE: Seeding against a specific controller so replication lag cannot confuse a re-read .NOTES Author: Jeffrey Stuhr Blog: https://www.techbyjeff.net LinkedIn: https://www.linkedin.com/in/jeffrey-stuhr-034214aa/ #> [CmdletBinding()] [OutputType('ADEnvironmentConnection')] param( [Parameter()] [ValidateNotNullOrEmpty()] [string]$Server, [Parameter()] [System.Management.Automation.PSCredential]$Credential, [Parameter()] [switch]$InstallRsat, # Names every object this provider creates except the human user accounts, and names the # container they all live in. Setting it moves the whole tree, which is why it belongs # on the connection rather than on each seeding command. [Parameter()] [ValidatePattern('^[A-Za-z0-9][A-Za-z0-9_-]*[-_]$')] [string]$Prefix = $script:TestEnvironmentDefaultPrefix, [Parameter()] [switch]$PassThru ) # Windows only, and worth saying plainly. The RSAT modules do not exist elsewhere, and the # error from a failed import would otherwise be about a missing module rather than a # platform that was never going to work. if ($PSVersionTable.PSEdition -eq 'Core' -and -not $IsWindows) { Write-Error 'The AD provider needs the RSAT ActiveDirectory and GroupPolicy modules, which exist only on Windows. The Entra provider runs anywhere.' -ErrorAction Stop return } foreach ($name in 'ActiveDirectory', 'GroupPolicy') { if (Get-Module -Name $name) { continue } try { Import-Module -Name $name -ErrorAction Stop -Verbose:$false Write-Verbose "Imported $name" } catch { if ($InstallRsat) { Write-Verbose "Importing $name failed; attempting to install the RSAT feature" if (-not (Install-ADTestRsatFeature -Module $name)) { Write-Error "Could not install the RSAT feature providing $name. Install it by hand, then connect again." -ErrorAction Stop return } Import-Module -Name $name -ErrorAction Stop -Verbose:$false } else { Write-Error ("The $name module is not available, so the AD provider cannot run. " + 'Install RSAT (Add-WindowsCapability -Online -Name Rsat.ActiveDirectory.DS-LDS.Tools ' + 'and Rsat.GroupPolicy.Management.Tools), or re-run with -InstallRsat.') -ErrorAction Stop return } } } if (-not (Test-ADTestAdministrator)) { Write-Error 'The AD provider needs an elevated session: creating organisational units, users and Group Policy objects all require it.' -ErrorAction Stop return } # Cleared first, so a failed reconnect cannot leave the previous domain in place and send # the next command somewhere the caller has stopped thinking about. $script:ADConnection = $null try { $domainParameter = @{ ErrorAction = 'Stop' } if ($Server) { $domainParameter['Server'] = $Server } if ($Credential) { $domainParameter['Credential'] = $Credential } $domain = Get-ADDomain @domainParameter } catch { Write-Error "Could not reach a domain: $($_.Exception.Message)" -ErrorAction Stop return } # Set before the connection is recorded, because every distinguished name in this provider # is built from it. $script:ADTestRootName = '{0}TestData' -f $Prefix $script:ADConnection = [PSCustomObject]@{ PSTypeName = 'ADEnvironmentConnection' Prefix = $Prefix RootOU = $script:ADTestRootName SeedTag = (Get-TestSeedMarker -Prefix $Prefix).Tag DNSName = $domain.DNSRoot DomainDN = $domain.DistinguishedName NetBIOSName = $domain.NetBIOSName Server = if ($Server) { $Server } else { $domain.PDCEmulator } Credential = $Credential ConnectedAt = Get-Date } # Checked after the domain rather than before, because a broken install and an unreachable # domain are different problems and the domain is the one people actually hit. $null = Get-ADTestDataPath Write-Verbose "Connected to $($domain.DNSRoot) ($($domain.DistinguishedName))" if ($PassThru) { return $script:ADConnection } } |