1+ function Resolve-LS2ConnectionContext {
2+ <#
3+ . SYNOPSIS
4+ Detects the appropriate forest name and credential for a Locksmith2 scan.
5+
6+ . DESCRIPTION
7+ Applies a prioritized detection strategy to determine the correct AD forest
8+ and credential to use:
9+
10+ 1. Both -Forest and -Credential supplied at CLI -> Explicit
11+ 2. -Credential only (no -Forest) -> ExplicitCredential (forest derived from UserName)
12+ 3. -Forest only; current user is domain user -> DomainUser (no credential)
13+ 4. Neither; current user is domain user -> DomainUser (forest from GetCurrentDomain)
14+ 5. Non-domain user, domain-joined machine -> DomainComputer (machine account auth, no credential)
15+ 6. Non-domain user, non-domain machine, interactive -> PromptedAll (prompt for both)
16+ 7. Non-domain user, non-domain machine, non-interactive -> terminating error
17+
18+ When running interactively, failed RootDSE binds trigger up to 3 retry prompts.
19+
20+ . PARAMETER Forest
21+ Optional. DNS name of the target AD forest. If omitted, auto-detection is used.
22+
23+ . PARAMETER Credential
24+ Optional. PSCredential for the scan. If omitted, auto-detection is used.
25+
26+ . OUTPUTS
27+ System.Collections.Hashtable with keys: Forest, Credential, Method
28+
29+ . EXAMPLE
30+ $ctx = Resolve-LS2ConnectionContext
31+ Initialize-LS2Scan -Forest $ctx.Forest -Credential $ctx.Credential
32+
33+ . NOTES
34+ Method values: Explicit | ExplicitCredential | DomainUser | DomainComputer | PromptedAll
35+ #>
36+ [CmdletBinding ()]
37+ [OutputType ([hashtable ])]
38+ param (
39+ [Parameter ()]
40+ [string ]$Forest ,
41+
42+ [Parameter ()]
43+ [System.Management.Automation.PSCredential ]$Credential
44+ )
45+
46+ # -------------------------------------------------------------------------
47+ # Short-circuit: both explicitly supplied
48+ if ($Forest -and $Credential ) {
49+ return @ {
50+ Forest = $Forest
51+ Credential = $Credential
52+ Method = ' Explicit'
53+ }
54+ }
55+
56+ # -------------------------------------------------------------------------
57+ # Credential-only: derive forest from UserName (DOMAIN\user or user@domain.com)
58+ if (-not $Forest -and $Credential ) {
59+ $derivedForest = if ($Credential.UserName -match ' ^([^\\]+)\\' ) {
60+ $Matches [1 ]
61+ } elseif ($Credential.UserName -match ' @(.+)$' ) {
62+ $Matches [1 ]
63+ } else {
64+ $Credential.UserName
65+ }
66+ return @ {
67+ Forest = $derivedForest
68+ Credential = $Credential
69+ Method = ' ExplicitCredential'
70+ }
71+ }
72+
73+ # -------------------------------------------------------------------------
74+ # Forest-only or neither: run detection
75+ $maxAttempts = 3
76+ $attempt = 0
77+ $resolvedForest = $Forest # may be $null if neither was supplied
78+
79+ do {
80+ $attempt ++
81+
82+ # Step 1: domain user path
83+ if (Test-IsDomainUser ) {
84+ if (-not $resolvedForest ) {
85+ try {
86+ $resolvedForest = [System.DirectoryServices.ActiveDirectory.Domain ]::GetCurrentDomain().Name
87+ } catch {
88+ $PSCmdlet.WriteError (
89+ [System.Management.Automation.ErrorRecord ]::new(
90+ [System.Exception ]::new(' Unable to determine current domain. Supply -Forest explicitly.' ),
91+ ' DomainDiscoveryFailed' ,
92+ [System.Management.Automation.ErrorCategory ]::ObjectNotFound,
93+ $null
94+ )
95+ )
96+ return
97+ }
98+ }
99+ $script :CredentialResolved = $true
100+ return @ {
101+ Forest = $resolvedForest
102+ Credential = $null
103+ Method = ' DomainUser'
104+ }
105+ }
106+
107+ # Step 2: domain-joined machine — authenticate via computer account
108+ if (Test-IsDomainComputer ) {
109+ $computerInfo = Get-CimInstance - ClassName Win32_ComputerSystem - ErrorAction SilentlyContinue
110+ $machineDomain = $computerInfo.Domain
111+
112+ $script :CredentialResolved = $true
113+ return @ {
114+ Forest = if ($resolvedForest ) { $resolvedForest } else { $machineDomain }
115+ Credential = $null
116+ Method = ' DomainComputer'
117+ }
118+ }
119+
120+ # Step 3: non-domain machine — must prompt for both
121+ if (Test-IsInteractiveSession ) {
122+ $promptedForest = Read-Host - Prompt ' Enter the target AD forest DNS name'
123+ Write-Host ' '
124+ Write-Host ' Windows PowerShell credential request'
125+ Write-Host " Enter credentials for forest '$promptedForest '"
126+ $promptedUser = Read-Host ' User (DOMAIN\username or user@domain.com)'
127+ $promptedPass = Read-Host " Password for user $promptedUser " - AsSecureString
128+ $promptedCred = [System.Management.Automation.PSCredential ]::new($promptedUser , $promptedPass )
129+
130+ if ($promptedForest -and $promptedCred ) {
131+ $ctx = @ {
132+ Forest = $promptedForest
133+ Credential = $promptedCred
134+ Method = ' PromptedAll'
135+ }
136+ # Validate via RootDSE bind
137+ $testRootDSE = Get-RootDSE - Forest $ctx.Forest - Credential $ctx.Credential - ErrorAction SilentlyContinue
138+ if ($testRootDSE ) {
139+ return $ctx
140+ }
141+ Write-Warning " RootDSE bind failed for '$promptedForest '. Attempt $attempt of $maxAttempts ."
142+ continue
143+ }
144+ }
145+
146+ # Non-interactive, non-domain — nothing we can do
147+ $PSCmdlet.ThrowTerminatingError (
148+ [System.Management.Automation.ErrorRecord ]::new(
149+ [System.Exception ]::new(' Cannot resolve connection context in non-interactive session on a non-domain machine. Supply -Forest and -Credential explicitly.' ),
150+ ' NonInteractiveResolutionFailed' ,
151+ [System.Management.Automation.ErrorCategory ]::AuthenticationError,
152+ $null
153+ )
154+ )
155+ return
156+
157+ } while ($attempt -lt $maxAttempts )
158+
159+ # Exhausted all attempts
160+ $PSCmdlet.ThrowTerminatingError (
161+ [System.Management.Automation.ErrorRecord ]::new(
162+ [System.Exception ]::new(" Failed to establish a valid connection context after $maxAttempts attempts. Supply -Forest and -Credential explicitly." ),
163+ ' ConnectionContextResolutionExhausted' ,
164+ [System.Management.Automation.ErrorCategory ]::AuthenticationError,
165+ $null
166+ )
167+ )
168+ }
0 commit comments