Powershell (Invoke-Parallel problems)

Hello there,

My ultimate goal of this script is to be able to  - across many hundreds of machines - simultaneously - 1) uninstall the 2007 SCCM client. 2) utterly clear it from a machine (WMI, registry, file system) and finally 3) reinstall it. All of the above steps should be done remotely with NO restarts or impacts to the end users (in otherwords they should have no idea it's happening at all)

I am absolute rubbish at powershell (it's my first language) but I have built up a level of baseline competency and have a (stupidly large) script somewhat written which I will link at the end... while it doesn't do everything i want it to (namely a check at the end of the installation to see if it was successful or not and write that to the log)... I have hit a snag in getting it to function at all.. most specifically at the section where I am stepping through each "check if the OS is supported" then "Check if WMI is consistent" then "uninstall the client" etc.etc.

Each relies on the previous.. in otherwords I need only those machines who have succeeded in their ping AND OS check AND WMI check to go through the uninstall/reinstall process... however i do need ALL machines who are run through the script to log any failures both in those above checks, and (if they make it through to the uninstall/install functions) during the uninstall/install process to the csv log file.

I have zipped up the entire thing (as it fairly involved, needs to load in my module of functions and has the sccm client installation folder itself in there.. [in order to avoid tryring to get CredSSP to work -- plus we don't even have winRM enabled in our environment anyway].

Please take a look from line 871 onwards (apologies in advance for... well.. you'll see how crap I am at coding lol)

<<Tried to upload files... my company blocks SO many websites for this... may have to upload from home>>

  • Edited by KickinCrispy Friday, July 03, 2015 7:05 AM accidently a few words
July 3rd, 2015 7:01am

Thanks Satyajit,

1) I did initially have a version of the script that worked sequentially and ran each function based on a few foreach statements, but the script has blown out since then... so I know my functions work.. I am just struggling with getting the array information INTO and OUT of each invokeParallel script block.

2) Unfortunately we must use invoke-parallel because the entire process takes SO unbelievably long to complete, even for a single machine. Running it against 100 machines or so could easily take an entire day to complete.

3) You lost me completely :(

Jrv, normally I would in a heartbeat do as you say, but the issue is not so much with SCCM, as it is with my inability to code.. but I suppose you are right and they may have some solution to this problem already coded somewhere, but i doubt it will be muiltithreaded which is what we really need..

Here is the code I am having troubles with.. have tried to include as much of it as i can.. things like $WorkingDirectory are not shown though.. that is the workingdirectory of the script when launched.

#Get our list of machines if it exists..
	if (test-path -path "$inputfile" -erroraction SilentlyContinue) {
		$MasterList = gc $inputfile
		
		# If the list we just got IS NOT empty...
		if ($MasterList -ne $null) {
			Write-Host -foregroundcolor Green "Found: $Inputfile"
			$MasterList
			Start-sleep -seconds 2
			
			foreach ($item in $Masterlist) {
				$test = test-connection -computername $item -quiet -count 1
				if ($test) {
				"$item,Ping Succeeded,status,$(Get-Date -format "dd_MM_yyyy_HH:mm:ss:$((Get-Date).millisecond)")" | Out-File -literalpath $Resultfile -append
				Write-Host -foregroundcolor green "$Item Successfully Pinged"
				$PingingComputers += $Item
				}
				else {
				"$item,Ping Failed,error,$(Get-Date -format "dd_MM_yyyy_HH:mm:ss:$((Get-Date).millisecond)")" | Out-File -literalpath $Resultfile -append
				Write-Host -foregroundcolor red "$Item Failed Ping"
				}
			}	
	
	##								#	##								#	##								#	##								#	##								#	##								#
	#	THE BROKEN SECTION OF CODE	#	#	THE BROKEN SECTION OF CODE	#	#	THE BROKEN SECTION OF CODE	#	#	THE BROKEN SECTION OF CODE	#	#	THE BROKEN SECTION OF CODE	#	#	THE BROKEN SECTION OF CODE	#
	##								#	##								#	##								#	##								#	##								#	##								#

			# create empty array
			$Sublist2 = @()
			
			# if any machines pinged at all
			if ($PingingComputers -ne $null) {
			
				# Create objects for every item in $Sublist and populate empty array $Sublist2 with those objects with an empty property to be filled by the upcoming OS check "OS"
				$PingingComputers |%{$Sublist2 += new-object PSObject -Property @{
					Name = $_
					OS = ""
					}
				}

				# Start Multithread scriptblock for OS check
				$Sublist2 | Invoke-Parallel -throttle $OSCheckThrottle -RunspaceTimeout $OScheckRunspaceTimeout -NoCloseOnTimeout -importvariables -importmodules -scriptblock {
				# Start Multithread scriptblock for OS check
				
					$OperatingSys = Get-WmiObject -ComputerName "$($_.Name)" -Class Win32_OperatingSystem -ev $err
					$OperatingSys

					#if we didn't throw trying to do the check then...
					if ($err -eq $Null) {
						if ($OperatingSys.Caption -notlike "*2003*" -and $OperatingSys.Caption -notlike "*XP*" -and $OperatingSys.Caption -notlike "*10*") {
							$_.OS = $True
							"$($_.Name),Found Supported OS $($Operatingsys.Caption),status,$(Get-Date -format "dd_MM_yyyy_HH:mm:ss:$((Get-Date).millisecond)")" | Out-File -literalpath $Resultfile -append
						}
						else {
							$_.OS = $False
							"$($_.Name),Found Unsupported OS $($Operatingsys.Caption),error,$(Get-Date -format "dd_MM_yyyy_HH:mm:ss:$((Get-Date).millisecond)")" | Out-File -literalpath $Resultfile -append
						}
					}
					else {
						"$($_.Name),Failed to perform OS Check: $err,error,$(Get-Date -format "dd_MM_yyyy_HH:mm:ss:$((Get-Date).millisecond)")" | Out-File -literalpath $Resultfile -append
					}
				# end multithread scriptblock for OS check	
				}
				# end multithread scriptblock for OS check
				
				$SuccessfulOSCheck = @()

				$SuccessfulOSCheck = $Sublist2 | where {$_.OS -eq $True}
				$SuccessfulOSCheck = $SuccessfulOSCheck.Name
				
				if ($SuccessfulOSCheck -ne $null) {
					Write-Host -Foregroundcolor green "Successful OS Check Computers:"
					$SuccessfulOSCheck
					
					# Count em' to Host:
					Write-Host -foregroundcolor cyan "Count of Supported OS"
					$SuccessfulOSCheck.count
					Start-sleep -seconds 2
				}
		
				
				
				$Sublist3 = @()

				if ($SuccessfulOSCheck -ne $null) {
						$SuccessfulOSCheck |%{$Sublist3 += new-object PSObject -Property @{
							Name = $_
							State = ""
							}
						}							

					$Sublist3 | Invoke-Parallel -throttle $WMICheckThrottle -RunspaceTimeout $WMICheckRunspaceTimeout -NoCloseOnTimeout -importvariables -importmodules -scriptblock {
						Set-Alias psexec "$WorkingDirectory\psexec\psexec.exe"
						
						$computername = "\\$($_.Name)"
						$Scriptblock = "winmgmt /verifyrepository"

						## Prepare the command line for PsExec. We use the XML output encoding so that PowerShell can convert the output back into structured objects.
						## PowerShell expects that you pass it some input when being run by PsExec this way, so the 'echo .' statement satisfies that appetite.
						$commandLine = "echo . | powershell -Output XML "

						## Convert the command into an encoded command for PowerShell
						$commandBytes = [System.Text.Encoding]::Unicode.GetBytes($scriptblock)
						$encodedCommand = [Convert]::ToBase64String($commandBytes)
						$commandLine += "-EncodedCommand $encodedCommand"

						## Collect the output and error output
						$errorOutput = [IO.Path]::GetTempFileName()
						$output = psexec /acceptEula $computername cmd /c $commandLine 2>$errorOutput

						## Check for any errors
						$errorContent = Get-Content $errorOutput
						Remove-Item $errorOutput

						if($errorContent -match "(Access is denied)|(failure)|(Couldn't)") {
							$OFS = "`n"
							$errorMessage = "Could not execute remote expression. "
							$errorMessage += "Ensure that your account has administrative " +
								"privileges on the target machine.`n"
							$errorMessage += ($errorContent -match "psexec.exe :")

							Write-Error $errorMessage
						}
						## Return the output for use in if statement
						if ($output -eq "WMI repository is consistent") {
							$_.State = $true
						}
						else {
							$_.State = $false
						}
					# END Sublist3 scriptblock for WMI check
					}
					# END Sublist3 scriptblock for WMI check

					$SuccessfulWMICheck = @()
					
					$SuccessfulWMICheck += $Sublist3 | where {$_.State -eq $True}
					$SuccessfulWMICheck = $SuccessfulWMICheck.Name
				
					if ($SuccessfulWMICheck -ne $null) {
						Write-Host -Foregroundcolor green "Successful WMI Check Computers:"
						$SuccessfulWMICheck
					
						# Count em' to Host:
						Write-Host -foregroundcolor cyan "Count of Successful WMI check:"
						$SuccessfulWMICheck.count
						Start-sleep -seconds 2
					}

	
	##								       #	##	##								       #	##	##								       	##								       ##
	#	END OF THE BROKEN SECTION OF CODE	#	#	#	END OF THE BROKEN SECTION OF CODE	#	#	#	END OF THE BROKEN SECTION OF CODE  	#	END OF THE BROKEN SECTION OF CODE   #
	##								       #	##	##								       #	##	##								       	##								       ##

					# create empty array
					$Sublist4 = @()

					if ($SuccessfulWMICheck -ne $null) {
						$SuccessfulWMICheck |%{$Sublist4 += new-object PSObject -Property @{
							Name = $_
							Installed = ""
							}
						}

						# begin multithread Remediation scriptblock
						$Sublist4 | Invoke-Parallel -throttle $RemediationThrottle -RunspaceTimeout $RemediationRunspaceTimeout -NoCloseOnTimeout -importvariables -importmodules -scriptblock {
						# begin multithread Remediation scriptblock
						
							# Write to log we are starting the process of remediation for this machine.
							"$($_.Name),Starting Process,status,$(Get-Date -format "dd_MM_yyyy_HH:mm:ss:$((Get-Date).millisecond)")" | Out-file -literalpath $Resultfile -append

							# Scriptblock is treated as new session of powershell needs to set alias in here instead of outside.
							Set-Alias psexec "$WorkingDirectory\psexec\psexec.exe"

							# in order to avoid the whole CredSSP bullshit, and since we don't have winRM enabled, we gotta send client installation files to machine's C:\temp
							Copy-SCCMClientInstallation

							# stop related services
							Stop-OurServices

							#Remove-qmgrdatfiles
							#Remove-WUSoftwareDistributionFolder
							Rename-WUSoftwareDistributionFolder
							#Remove-CatRoot2Folder

							#Reset-securitydescriptorbits
							#Reset-securitydescriptorwuauserv

							#Send-RegisterDLLs

							Start-OurServices

							#reset-Bitsadmin

							# The regular uninstall falls over sometimes clearing cache folder, we do it manually for SCCM such that when IT attempts to do it, there are no problems.
							Remove-CCMCacheFolder

							Uninstall-SCCMClient

							# This is a series of cleanup tasks that should run after the regular uninstall methods to make SURE sccm client is gone before we reinstall the client.
							Remove-CCMSetupFolder
							Remove-CCMFolder
							Remove-CCMNamespace
							Remove-SMSNamespace

							# reinstall the client via psexec
							Install-SCCMClient

							# attempt to force windows update to update.. so far this doesn't ever appear to have done jack shit but it's part of microsoft's article to repair WUAgent..
							Send-WUAgentDetectnow
							
							# If theres a log file/s open for that machine at the end of this process, then wait $seconds and close it/them.
							$Trace32Presence = Get-process trace32 | where {$_.MainWindowTitle -like "*$($_.Name)*"}
							if ($Trace32Presence) {
								$seconds = 3
								Start-sleep -seconds $seconds
								$Trace32Presence | Stop-Process
							}
							
							#-----------------------------------------------------#
							#-----------------------------------------------------#
							#-----------------------------------------------------#
							# 		 Start "installation successful?" check
							#-----------------------------------------------------#
							#-----------------------------------------------------#
							#-----------------------------------------------------#
							
							#"$($_.Name),Checking Installation Status,finalstatuscheck,$(Get-Date -format "dd_MM_yyyy_HH:mm:ss:$((Get-Date).millisecond)")" | Out-file -literalpath $Resultfile -append
							#Start-Sleep -seconds 300
							
							#Get-SCCMInstallationStatus
						
							#-----------------------------------------------------#
							#-----------------------------------------------------#
							#-----------------------------------------------------#
							# 		 End installation check
							#-----------------------------------------------------#
							#-----------------------------------------------------#
							#-----------------------------------------------------#
								
								
						# END Remediation PARALLEL SCRIPT BLOCK HERE
						}
						# END Remediation PARALLEL SCRIPT BLOCK HERE
					}
					else {
						Write-Host -foregroundcolor red "No Machines passed their WMI Check, cannot proceed"
					}
				}
				else {
					Write-Host -foregroundcolor red "No Machines passed their OS Check, cannot proceed"
				}
			}
			else {
				Write-Host -foregroundcolor red "No Machines pinged, cannot proceed"
			}
		}
		else {
			Write-Host -foregroundcolor red "servers.txt file: $inputfile is EMPTY $(Get-Date -format "dd_MM_yyyy_HH:mm:ss:$((Get-Date).millisecond)")"
		}
	# open the log path after 2 seconds...
	explorer.exe $logfolder
	}
	else {
		Write-Host -foregroundcolor red "No servers.txt file found in: $WorkingDirectory\Input\ $(Get-Date -format "dd_MM_yyyy_HH:mm:ss:$((Get-Date).millisecond)")"
	}

Free Windows Admin Tool Kit Click here and download it now
July 5th, 2015 9:56pm

As noted earlier we cannot fix your code for you.  Isolate the lines that are failing and post the error.  If you need help figuring out how to do that you will need to hire a consultant to work with you on a fix.

Please understand that what you are asking is beyond the scope of this forum.

July 5th, 2015 10:51pm

Hi,

Point 3, I was refering to Dot Sourcing. Helps clean things out.

Read about Dot Sourcing: (don't have to do it now though)

http://blogs.technet.com/b/heyscriptingguy/archive/2009/12/23/hey-scripting-guy-december-23-2009.aspx

Regarding the variables\array\object issues. Check if using global,script variables helps in your case.

As jrv mentioned please be specific, on which portion of the script you are not getting the expected results. What is your expected result.

At last I don't think windows powershell has any cmdlet called Invoke-Parallel. It must be hence some custom function or script that you are using, without what and how it does that, its not possible to comment  on the working as well.

Free Windows Admin Tool Kit Click here and download it now
July 6th, 2015 12:04am

Hi,

I would say start with small simpler codes, as one below, which very well does parallel processing.

For 100 machines this should be enough, no need to dig into runspaces. You can put your code inside script block part.

$computers = Get-ADComputer -filter * |Select-Object -ExpandProperty dnsHostName
$creds = Get-Credential domain\user
foreach($computer in $computers)
{
    $session = New-PSSession -ComputerName $computer -Credential $creds
    Invoke-Command -Session $session -ScriptBlock { w32tm /resync /nowait /rediscover }
}

NOTE:- One computer allowes limited PSsessions at a time.

References:

How do I run my PowerShell scripts in parallel without using Jobs?

http://serverfault.com/questions/626711/how-do-i-run-my-powershell-scripts-in-parallel-without-using-jobs

July 6th, 2015 12:29am

Fair enough, sorry to bother you guys : ( I will try to sort it out..

As it is currently here is the output I get:

imgur DOT com / JXPgQ7p

notice how the machine names are smushed together at the "Successful OS Check Computers" bit... also naturally the WMI check will fail (I just cancelled with CTRL+C) because "MELYDEVTSE141MELYDEVTSE483" is not a machine name..

also when the OS check is being run (which is all of about a second) usually when multithreading 2 items, you get 2 threads, this only seems to run the 1 thread, and then I get the runspace data error shown in the screenshot.

Any assistance at all most welcome.

p.s. Sup son! \_()_/  :)

Free Windows Admin Tool Kit Click here and download it now
July 6th, 2015 1:04am

You're right, that function is not native to powershell:

github DOT com / RamblingCookieMonster / Invoke-Parallel / tree / master / Invoke-Parallel

I've essentially just taken that code and dumped the function into my script.

I originally tried with powershell jobs, but ended up being comparably as slow as running sequentially so I dumped that idea in favour of the above. I mean the idea is brilliant, drop any code into the scriptblock and it will run against whatever the invoke-parallel was fed from the pipeline.. and I have managed to get it working before when I wasn't interested in getting the success/failure of the processes INSIDE the scriptblock out afterwards... but soon as I try to get this stuff out it all goes pear shaped.

I will check out the dot sourcing when I have some time, everything extra I have to learn is just giving me a headache :(

July 6th, 2015 1:27am

Doesn't invoke-command use winRM/WSMAN though? (they're the same thing right?) -- we don't have winRM enabled in our environment so I cannot use invoke-command :( Runspace pooling seems to be the only other option, as I'm not going down the powershell jobs route again...

Because I can't use Powershell remoteing, it's more difficult to run msiexec commands I want to run on each individual machine, and so portions of the script use psexec -- which doesn't return control to powershell until it's done it's thing.... so i think wrapping the invoke-command inside a foreach isn't going to solve those sort of issues, and .. wouldn't using a foreach negate the parallel-ness.. ? The above would still run sequentially I think  - which is not what I needed.

Free Windows Admin Tool Kit Click here and download it now
July 6th, 2015 1:41am

This topic is archived. No further replies will be accepted.

Other recent topics Other recent topics