Search This Blog

Mar 14, 2025

Running AD cmdlets within foreach parallel script block



 Powershell's parallel foreach script block runs in its own runspace so anything defined outside of the block is not visible in it. A few steps to make AD cmdlets work:


  1. Import activedirectory module within the block. It may throw warning "Error initializing default drive", which can be safely ignored but you will have to
  2. Specify DC to establish connection via -server parameter in get-ad* cmdlets
    get-aduser -server "DC1.foobar.com" -.....
  3. The runspace won't have your credential from main session either so you have to transfer credential explicitly into script block

    $cred = get-credential
    $users | foreach -parallel {
          get-aduser -identity $_.samAccountName -credential $using:cred
    }
  4. If there are too many concurrent connections to AD, some connections may fail. Tweak to find the ThrottleLimit that works for you. 
  5. Use inputObject to return result
    $_ | add-member -notepropertyname "pn" -notepropertyValue "pv"
  6. Putting it altogether
    $cred = get-credential
    $users | foreach -parallel {
          import-module ActiveDictory
          $u=get-aduser -identity $_.samAccountName -credential $using:cred
          $_ | add-member -NotePropertyName "DN" -NotePropertyValue $u.distinguishedName
    } -ThrottleLimit 5

[UPDATE]

So limiting the number of threads is not ideal, with the number as long as 2, there is still chance where connection be refused by DC, not to mention we lost most of benefit if the number is too low.

One workaround is to make sure only one runspace connects to a particular DC at a time. This can be achieved by using a file as a lock. First get list of all DCs in a domain, then when a connection is made to a DC, obtain an exclusive handle to a file that represents the DC (e.g. "dc01.lock"). Once finish access the DC, release the lock file.

# Acquire lock before connecting to a DC
$server = $null
while ($null -eq $server){                   
  foreach ($DC in $using:dcs) {
    try {
          $lockFile = [system.io.file]::open("c:\temp\$($DC).lock",
                         'OpenOrCreate','ReadWrite','None')
          $server = $DC
          break
    }catch{                }
}
if($null -eq $server) {Start-Sleep -Milliseconds 50}
}
try {
    get-aduser -server $server ....

    # Release lock
    $lockFile.close()
    remove-item "c:\temp\$($server).lock" -force -erroraction silentlyContinue
   
}catch {}



There are other ways to implement a lock, such as described in Dave's blog, but above file lock works very well and is less complicated.