Speed up script

This topic contains 6 replies, has 4 voices, and was last updated by  Gary 3 weeks, 3 days ago.

  • Author
    Posts
  • #99852

    Gary
    Participant

    Hi Guys. I am running this section of my script to look up a list of computers in AD but as there are so many objects and I have to search them against a whole bunch of domains it takes forever. I was wondering if there might be a way to speed it up like searching more then one computer at a time in the 'Foreach $CollectionMembersModify step.

    Foreach ($Domain in $SearchDomains){
    	Foreach ($Computer in $CollectionMembersModify){
    		Try {
    				IF($Domain -eq "domain.local"){
    				#Temporary soluton until trusts are setup for this domain.
    					$ComputerExists = Get-ADComputer -Identity $Computer -Server $Domain -Credential $AdminCredsCIC -Properties Name,Enabled
    				}
    				Else{
    					$ComputerExists = Get-ADComputer -Identity $Computer -Server $Domain -Properties Name,Enabled
    				}
    				If($ComputerExists) {
    					#Add computer to Array for the removal step (Filter)
    					Write-Host "$Computer - Found in $Domain" -ForegroundColor DarkGreen
    					$LocatedComputerArray += $Computer
    					
    					#Add to new custom object to later remove disabled.
    					$TempInfo = New-Object PSObject
    					$TempInfo | Add-Member -MemberType NoteProperty -Name Name -Value $ComputerExists.Name
    					$TempInfo | Add-Member -MemberType NoteProperty -Name Domain -Value $Domain
    					$TempInfo | Add-Member -MemberType NoteProperty -Name Enabled -Value $ComputerExists.Enabled
    						$CustomInfo += $TempInfo
    						Remove-Variable -Name TempInfo
    				}
    			}
    		Catch{
    			Write-Host "$Computer - Not found in $Domain" -ForegroundColor Red -BackgroundColor Yellow
    		}
    		}
    }
    
  • #99853

    Gary
    Participant

    I thought I just found a solution in workflow with -Parallel switch, but workflows looks like its going to run every computer in one go... with about 5000 objects that would crash for sure.

    Is there something like that but would maybe do 10 at a time or something more realistic?

  • #99861

    Hi

    How about foreach -parallel?

    https://docs.microsoft.com/en-us/powershell/module/psworkflow/about/about_foreach-parallel?view=powershell-5.1

    If I remember correctly you can set the amount of parallel items at the time.
    Jake

    • #99918

      Gary
      Participant

      Thanks. That didn't show up when I viewed the support file. I did find it elsewhere and the switch is -throttlelimit x for anyone else looking for this.
      This switch is only supported in version 4 as far as I am aware but I haven't had the opportunity to test this feature out yet.

  • #99864

    Joel Sallow
    Participant

    Although a slightly more involved solution, runspaces are also an available option, and they have a native ability to throttle how many tasks are running at the same time.

    For an easier handling of them, you can find the PoshRSJob module in the PSGallery.

  • #99948

    Rob Simmers
    Participant

    Outside of running things in parallel, you could make some improvements in your loop. You typically want to avoid the += when trying to build objects, it's much more efficient to capture them in a variable from your loop (e.g. $results). Rather than have two commands and add a param, you should look at splatting which allow you to dynamically add (or remove) parameters. Lastly, Write-Host isn't efficient or recommended either. You should consider returning a object for failures so that you can actually capture those and do something with them.

    $results = foreach ( $Domain in $SearchDomains ){
    	foreach ( $Computer in $CollectionMembersModify ){
    		try {
    				
                $cmpParams = @{
                    Identity   = $Computer 
                    Server     = $Domain 
                    Properties = @('Name', 'Enabled')
                }
                
                if ( $Domain -eq "domain.local" ){
    				$cmpParams.Add('Credential', $AdminCredsCIC)
    			}
    
    			$ComputerExists = Get-ADComputer @cmpParams
    
    			if ( $ComputerExists ) {
    				#Add computer to Array for the removal step (Filter)
    				Write-Host "$Computer - Found in $Domain" -ForegroundColor DarkGreen
    				
                    $ComputerExists | 
                    Select Name, 
                           Enabled, 
                           @{Name='Domain';Expression={$Domain}},
                           Message = 'FOUND'
                           	
    			}
    
    		}
    		catch {
    			Write-Host "$Computer - Not found in $Domain" -ForegroundColor Red -BackgroundColor Yellow
    
                New-Object -TypeName PSObject -Property @{
                    Name    = $Computer
                    Enabled = $null
                    Domain  = $Domain
                    Message = 'NOT_FOUND'
                }
    		}
    	}
    }
    
  • #100017

    Gary
    Participant

    My way was a little strange, but I'll show you what I was doing. I am basically searching an SCCM collection and finding any machines that don't exist in AD or are disabled and deleting them from SCCM.

    I didn't need to catch the errors because anything that's not found will end up in my collection at the end for removal.

    Id definitely still be curious about any tips you have as I'm always just working it out as I go.

    There are lots of useless write-hosts and logging but that was because we were having some connection issues that day and i really needed to know if it was still processing or hung.

    $CollectionID = "SMS00001"
    $LogPath = "C:\Temp\SCCM_Scripting_logs"
    $AdminCredsCIC = Get-Credential "Domain\user" #Temporary soluton until trusts are setup.
    #=====================================================================#
    
    $TimeStamp = Get-Date -Format FiledateTime
    $ArrayCountLog = "$LogPath\Array.$Timestamp.log"
    $LogFile = "$LogPath\$CollectionID.$Timestamp.log"
    $MissingComputerErrorLogFile = "$LogPath\MissingComputerErrorLogFile.$Timestamp.log"
    $DisabledComputerErrorLogFile = "$LogPath\DisabledComputerErrorLogFile.$Timestamp.log"
    $CollectionMembers = (Get-CMDevice -CollectionId $CollectionID).name
    
    $SearchDomains = @("Domain.local","Domain2.local","Domain3.local","Domain.local:3268")
    # Domain.local requires port to search sub domains but its slow, so search main domain first
    $LocatedComputerArray = @()
    $MissingComputerArray = @()
    $CustomInfo = @()
    $CollectionMembersModify = $CollectionMembers
    
    #region Back up.
    # Export Hostnames to Plain Text Log
    Write-Output "=== Backup Collection ===" | Out-File $LogFile -Append
    $CollectionMembers | Out-File $LogFile -Append
    # Export MOF 
    Export-CMCollection -CollectionId $CollectionID -ExportFilePath "$LogPath\$CollectionID.$Timestamp.mof"
    Write-Output "=== End Backup ===" | Out-File $LogFile -Append
    #Endregion
    
    ### DO NOT STOP THIS SECTION OF THE SCRIPT!!! ###
    ### It must run through ALL domains to be accurate ###
    #Region Search Domains
    Foreach ($Domain in $SearchDomains){
    	Foreach ($Computer in $CollectionMembersModify){
    		Try {
    				IF($Domain -eq "domain.local"){
    				#Temporary soluton until trusts are setup.
    					$ComputerExists = Get-ADComputer -Identity $Computer -Server $Domain -Credential $AdminCredsCIC -Properties Name,Enabled
    				}
    				Else{
    					$ComputerExists = Get-ADComputer -Identity $Computer -Server $Domain -Properties Name,Enabled
    				}
    				If($ComputerExists) {
    					#Add computer to Array for the removal step (Filter)
    					Write-Host "$Computer - Found in $Domain" -ForegroundColor DarkGreen
    					$LocatedComputerArray += $Computer
    					
    					#Add to new custom object to later remove disabled.
    					$TempInfo = New-Object PSObject
    					$TempInfo | Add-Member -MemberType NoteProperty -Name Name -Value $ComputerExists.Name
    					$TempInfo | Add-Member -MemberType NoteProperty -Name Domain -Value $Domain
    					$TempInfo | Add-Member -MemberType NoteProperty -Name Enabled -Value $ComputerExists.Enabled
    						$CustomInfo += $TempInfo
    						Remove-Variable -Name TempInfo
    				}
    			}
    		Catch{
    			Write-Host "$Computer - Not found in $Domain" -ForegroundColor Red -BackgroundColor Yellow
    		}
    		}
    #Logging
    Write-Host "Please wait while a new array is created that exludes already located computers. Speeds up searching in next domain" -ForegroundColor White -BackgroundColor Blue
    Write-Output "Array Count before $Domain Loop" | Out-File $ArrayCountLog -Append
    ($CollectionMembersModify).count | Out-File $ArrayCountLog -Append
    $CollectionMembersModify = $CollectionMembersModify | Where-Object {$LocatedComputerArray -notcontains $_ }
    Write-Output "Array Count after $Domain Loop" | Out-File $ArrayCountLog -Append
    ($CollectionMembersModify).count | Out-File $ArrayCountLog -Append
    }
    $CollectionMembersModify | Out-File "$LogPath\MissingComputers.$Timestamp.log"
    Write-Host "$($CustomInfo.Count) Total Computers Located on Domain" -ForegroundColor White -BackgroundColor Blue
    #Endregion
    ### END ###
    
    
    

    I will look at yours again later, i think i missed that your getting the results at the foreach domain part.. so it would work but would require me rethinking the catch section as i don't want a failed one for each domain, i just want it to count as a fail when its not found in any domain.
    Maybe i need to invert it for it to work..
    foreach ($computer in $computers){
    foreach ($domain in $domains){
    }
    }

You must be logged in to reply to this topic.