so basically, i am looking at doing a comparison between accounts we have in Active Directory and a csv which lists all active accounts (~15000 lines). This process would be ran weekly.
The issue that I am coming across is that runspaces do not seem to increase performance of the script after a certain point; i.e., before using multiple runspaces, the script took about 28-30 minutes to gather and cross reference data but after incorporating 2 runspaces, that dropped down to ~18 minutes.
So i thought – thats great, increase available runspaces and should see multiplicative decrease in time to process. Unfortunately, that isnt what happened, and as the script stands, I have increased the number of runspaces to 8, but only seen maybe 10 second performance increase.
Basically, I am getting the same performance out of 8 runspaces as I am getting out of 2 runspaces.
The server this runs on has 4 sockets, 2 cores per socket. Am i stupid? Missing something obvious? Or do i need to research and get a better handle on expected gains from multiple runspaces?
And yes, I know I am not using the term delta list accurately; changed process halfway through and never went back to correct var names 🙂
#Declarations
$DeltaListInactive = @() #these accounts exist in AD within the account OU, but are not actively enrolled accounts
$DeltaListActiveNotFound = @() #these accounts exist only in the CSV import - accounts have not been created or otherwise do not exist in AD, no actions will be processed against these accounts
$DeltaListActive = [System.Collections.Queue]::Synchronized([System.Collections.Queue]::new()) #these accounts exist in the CSV Sheet, and in AD. Actively enrolled and may require accounts changes based on CSV Sheet
$CSVImport= [System.Collections.Queue]::Synchronized($SynImport)
#region Information Gathering
##Build Comparison/Delta list
$ImportComparison = Compare-Object -ReferenceObject $CSVImport.UserID -DifferenceObject $ADImport.sAMAccountName -IncludeEqual
foreach($result in $ImportComparison)
{
if($result.SideIndicator -eq '=>') #these accounts are found in AD but do not exist in the CSV import; these accounts will be disabled and moved to the corresponding disabled account OU; logging for this process WILL NOT occur here
{
$DeltaListInactive += $result.InputObject
}
if($result.SideIndicator -eq '<=') #these accounts exist in the CSV import, but do not exist in AD; these accounts will not be processed in any way; logging for this process WILL occur here
{
$DeltaListActiveNotFound += $result.InputObject
$ActiveNotFoundLog += "`r`n$((Get-Date).ToString("hh:mm:ss")) // Account --> $($result.InputObject) <-- not found in Active Directory. Account will not be processed."
}
if($result.SideIndicator -eq '==') #these accounts are found in AD and the CSV import; these accounts will be checked for any pending changes and have them applied; logging for this process WILL NOT occur here
{
$DeltaListActive.Enqueue($result.InputObject)
}
}
$ActiveNotFoundLog | Out-File -FilePath $LogLocation$ActiveNotFoundLogFilename -Force -WhatIf:$false
$QueueCount = $DeltaListActive.Count
$ScriptBlock = {
param (
$DeltaListActive,
$CSVImport
)
while( $DeltaListActive.Count -gt 0 ) {
$Activeaccount = $DeltaListActive.Dequeue()
#Gather account information from AD
$ADUser = Get-ADUser -Identity $Activeaccount -Properties distinguishedName,memberof,surname,givenname,telephonenumber,enabled,employeeid,office
#declare change flags
[int]$ChangesRequired = 0
[bool]$ChangeStatus = $false
[bool]$ChangeOU = $false
[bool]$ChangeBuilding = $false
[bool]$ChangeGradYear = $false
[bool]$ChangeaccountID = $false
[bool]$ChangeGroups = $false
$CurrentADInfo = $null
$CurrentADInfo = [PSCustomObject]@{
AccountName = $ADUser.SamAccountName
Enabled = $ADUser.Enabled
OrgUnit = ($ADUser.DistinguishedName.Split(",") | Where-Object {-Not $_.StartsWith("CN=")}) -join ","
Building = $ADUser.Office
GradYear = $ADUser.telephonenumber
accountID = $ADUser.EmployeeID
Groups = ($ADUser.MemberOf | Get-ADGroup | Select-Object Name).Name
}
$CSVUser = $CSVImport | Where-Object UserID -eq $Activeaccount
$NewCSVInfo = $null
$NewCSVInfo = [PSCustomObject]@{
AccountName = $CSVUser.UserID
Enabled = "True"
OrgUnit = "OU=20$($CSVUser.'Year'),OU=accounts,OU=domain,DC=domain,DC=local"
Building = $CSVUser.'Building Number'
GradYear = "20$($CSVUser.'Year')"
accountID = $CSVUser.accountID
Groups = @("$($CSVUser.'Building Number')-accounts","accounts-Year')","Internet-All-accounts")
}
#Check for necessary changes and pass to action hash
#if account disabled, then set required status change flag to true
if(-not $CurrentADInfo.Enabled)
{
$ChangeStatus = $true
$ChangesRequired++
}
#if incorrect OU, then set change GradYear flag
if($CurrentADInfo.OrgUnit -ne $NewCSVInfo.OrgUnit)
{
$ChangeOU = $true
$ChangesRequired++
}
#if incorrect building, then set change building flag
if($CurrentADInfo.Building -ne $NewCSVInfo.Building)
{
$ChangeBuilding = $true
$ChangesRequired++
}
#if incorrect Gradyear, then set change gradyear flag
if($CurrentADInfo.GradYear -ne $NewCSVInfo.GradYear)
{
$ChangeGradYear = $true
$ChangesRequired++
}
#if incorrect accountid, then set change accountid flag
if($CurrentADInfo.accountID -ne $NewCSVInfo.accountID)
{
$ChangeaccountID = $true
$ChangesRequired++
}
#if incorrect groups, then set change groups flag
$CorrectGroupCount = 0
foreach($group in $NewCSVInfo.Groups)
{
if($CurrentADInfo.Groups -match $group)
{
$CorrectGroupCount++
}
}
if($CorrectGroupCount -ne $NewCSVInfo.Groups.Count)
{
$ChangeGroups = $true
$ChangesRequired++
}
$CurrentUserActions = [PSCustomObject]@{
account_Username = $ADUser.SamAccountName
Distinguished_Name = $ADUser.DistinguishedName
Changes_Required = $ChangesRequired
Status_Change_Required = $ChangeStatus
OU_Change_Required = $ChangeOU
Building_Change_Required = $ChangeBuilding
GradYear_Change_Required = $ChangeGradYear
accountID_Change_Required = $ChangeaccountID
Group_Change_Required = $ChangeGroups
OLD_ENABLED = $CurrentADInfo.Enabled
NEW_ENABLED = $NewCSVInfo.Enabled
OLD_OU = $CurrentADInfo.OrgUnit
NEW_OU = $NewCSVInfo.OrgUnit
OLD_BUILDING = $CurrentADInfo.Building
NEW_BUILDING = $NewCSVInfo.Building
OLD_GRADYEAR = $CurrentADInfo.GradYear
NEW_GRADYEAR = $NewCSVInfo.GradYear
OLD_accountID = $CurrentADInfo.accountID
NEW_accountID = $NewCSVInfo.accountID
OLD_GROUPS = $CurrentADInfo.Groups -join ','
NEW_GROUPS = $NewCSVInfo.Groups -join ','
}
if($ChangesRequired -gt 0)
{
$CurrentUserActions
}
}
}
$Inputs = New-Object 'System.Management.Automation.PSDataCollection[PSObject]'
$ActiveActionHash = New-Object 'System.Management.Automation.PSDataCollection[PSObject]'
$runspace_start = Get-Date
Write-Host "Starting runspaces... $($runspace_start.ToString("hh:mm:ss"))"
$Instances = @()
(1..8) | ForEach-Object { # (1..X) signifies the number of runspaces to create in order to process these requests
$Instance = [powershell]::Create().AddScript($ScriptBlock).AddParameter('DeltaListActive', $DeltaListActive).AddParameter('CSVImport',$CSVImport)
$Instances += New-Object PSObject -Property @{
Instance = $Instance
State = $Instance.BeginInvoke($Inputs,$ActiveActionHash)
}
}
while ( $Instances.State.IsCompleted -contains $False) {
# Report the accounts left in the queue
$time_spent = [Timespan]::FromMilliseconds(((Get-Date) - $runspace_start).TotalMilliseconds)
Write-Progress -Activity "Gathering account information..." -Status "account(s) Remaining: $($DeltaListActive.Count)/$($QueueCount)... $($time_spent.Hours)h $($time_spent.Minutes)m $($time_spent.Seconds)s" -PercentComplete (($DeltaListActive.count/$QueueCount)*100)
}
11