Gathering results into a .txt file

Hi Guys,

I’ve adopted this script to automate SCCM client repairs. All i’m trying to do is add a simple Out-File to capture names of any successful / failed devices. What i have right now doesn’t seem to create the file

 
########################################################### 
# AUTHOR  : Marius / Hican - http://www.hican.nl - @hicannl  
# DATE    : 07-05-2012  
# COMMENT : Automated remote repair of SCCM client on a 
#           list of machines, based on an input file. 
###########################################################

#ERROR REPORTING ALL
Set-StrictMode -Version latest
Set-ExecutionPolicy bypass
#----------------------------------------------------------
#STATIC VARIABLES
#----------------------------------------------------------
$SCRIPT_PARENT   = Split-Path -Parent $MyInvocation.MyCommand.Definition

#----------------------------------------------------------
#FUNCTION RepairSCCM
#----------------------------------------------------------

Function Repair_SCCM
{
  Write-Host "[INFO] Get the list of computers from the input file and store it in an array."
  $arrComputer = Get-Content ($SCRIPT_PARENT + "\input.txt")
  Foreach ($strComputer In $arrComputer)
  {
	#Put an asterisk (*) in front of the lines that need to be skipped in the input file.
    If ($strComputer.substring(0,1) -ne "*")
	{
	  Write-Host "[INFO] Starting trigger script for $strComputer."
	  Try
	  {
		$getProcess = Get-Process -Name ccmrepair* -ComputerName $strComputer
		If ($getProcess)
		{
		  Write-Host "[WARNING] SCCM Repair is already running. Script will end."
		  Exit 1
		}
		Else
		{
		  Write-Host "[INFO] Connect to the WMI Namespace on $strComputer."
   		  $SMSCli = [wmiclass] $SMSCli = [wmiclass] "\\$strComputer\root\ccm:sms_client"
		  Write-Host "[INFO] Trigger the SCCM Repair on $strComputer."
		  # The actual repair is put in a variable, to trap unwanted output.
		  $repair = $SMSCli.RepairClient()
		  Write-Host "[INFO] Successfully connected to the WMI Namespace and triggered the SCCM Repair on $strComputer."
		  ########## START - PROCESS / PROGRESS CHECK AND RUN
		  # Comment the lines below if it is unwanted to wait for each repair to finish and trigger multiple repairs quickly.
		  Write-Host "[INFO] Wait (a maximum of 7 minutes) for the repair to actually finish."
		  For ($i = 0; $i -le 470; $i++)
		  {
			$checkProcess = Get-Process -Name ccmrepair* -ComputerName $strComputer
			Start-Sleep 1
			Write-Progress -Activity "Repairing client $strComputer ..." -Status "Repair running for $i seconds ..."
			
		    If ($checkProcess -eq $Null)
			{
			  Write-Host "[INFO] SCCM Client repair ran for $i seconds."
			  Write-Host "[INFO] SCCM Client repair process ran successfully on $strComputer."
			  Write-Host "[INFO] Check \\$strComputer\c$\Windows\SysWOW64\CCM\Logs\repair-msi%.log to make sure it was successful."
              $strComputer | Out-File -Filepath "C:\Users\cp559\Downloads\Automated_Repair_SCCM_Client\repairresults.txt" -NoClobber -Append
   			} 
			ElseIf ($i -eq 470)
			{
              $strComputer | Out-file -Filepath "C:\Users\cp559\Downloads\Automated_Repair_SCCM_Client\repairfails.txt" -NoClobber -Append
			  Write-Host "[ERROR] Repair ran for more than 7 minutes. Script will end and process will be stopped."
			  Invoke-Command -Computer $strComputer { Get-Process -Name ccmrepair* | Stop-Process -Force }
              
   			  Exit 1
			} 
		  }
		  ########## END - PROCESS / PROGRESS CHECK AND RUN

		}
	  }
	  Catch
	  {
		Write-Host "[WARNING] Either the WMI Namespace connect or the SCCM Repair trigger returned an error."
		Write-Host "[WARNING] This is most likely caused, because there is already a repair trigger running."
		Write-Host "[WARNING] Wait a couple of minutes and try again."
		# If the script keeps throwing errors, the WMI Namespace on $strComputer might be corrupt.
	  }
    }
  }
}
# RUN SCRIPT 
Repair_SCCM
#Finished

I made sure i had the correct variable, by outputting it to the screen across various stages of the script, and it also creates the output, if i just create the variable and then directly output it, i.e

$strComputer = "abc"
$strComputer out-file | -Filepath "etc etc ect" -NoClobber -Append

But as soon as i place it back where it is now, nada, no output - Any ideas? I’m struggling with this one!

Thank you :slight_smile:

line 26

$arrComputer = Get-Content ($SCRIPT_PARENT + "\input.txt")

is reading from local profile folder not script folder. $arrComputer ends up reading nothing. This is because $Script_Parent is scoped within the Repair_SCCM function. To use the main script $Script_Parent, change your line 26 to look like:

$arrComputer = Get-Content ($Using:SCRIPT_PARENT + "\input.txt")

Hi Sam,

Changing that gave me the following error

A Using variable cannot be retrieved. A Using variable can be used only with Invoke-Command, Start-Job, or InlineScript in the script workflow. When it is used with Invoke-Command, the 
Using variable is valid only if the script block is invoked on a remote computer.
At C:\Users\cp559\Downloads\Automated_Repair_SCCM_Client\Automated_SCCM_Repairv2.ps1:23 char:3
+   $arrComputer = Get-Content ($Using:SCRIPT_PARENT + "\input.txt")
+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : UsingWithoutInvokeCommand

The script itself works ok - It’s the lines i added

$strComputer | Out-File -Filepath "C:\Users\cp559\Downloads\Automated_Repair_SCCM_Client\repairresults.txt" -NoClobber -Append

that don’t seem to capture the output of $strComputer. It’s only so i can get a basic txt file showing any failures / successes.

my bad, the diagnosis is correct - that’s the issue of variable scoping - $Script_Parent inside the function is not the same as $Script_Parent in the main script

the remedy was wrong. To pass the main script $Script_Parent variable to the function, you need to parameterize the function as in:

 
########################################################### 
# AUTHOR  : Marius / Hican - http://www.hican.nl - @hicannl  
# DATE    : 07-05-2012  
# COMMENT : Automated remote repair of SCCM client on a 
#           list of machines, based on an input file. 
###########################################################

#ERROR REPORTING ALL
# Set-StrictMode -Version latest
# Set-ExecutionPolicy bypass
#----------------------------------------------------------
#STATIC VARIABLES
#----------------------------------------------------------
$SCRIPT_PARENT   = Split-Path -Parent $MyInvocation.MyCommand.Definition 
#  $SCRIPT_PARENT # Folder where script is - not current folder


#----------------------------------------------------------
#FUNCTION RepairSCCM
#----------------------------------------------------------

Function Repair_SCCM
{

  [CmdletBinding()] 
  Param([Parameter(Mandatory=$true)][String]$SCRIPT_PARENT)

  Write-Host "[INFO] Get the list of computers from the input file and store it in an array."
  $arrComputer = Get-Content ($SCRIPT_PARENT + "\input.txt")
  Foreach ($strComputer In $arrComputer)
  {
	#Put an asterisk (*) in front of the lines that need to be skipped in the input file.
    If ($strComputer.substring(0,1) -ne "*")
	{
	  Write-Host "[INFO] Starting trigger script for $strComputer."
	  Try
	  {
		$getProcess = Get-Process -Name ccmrepair* -ComputerName $strComputer
		If ($getProcess)
		{
		  Write-Host "[WARNING] SCCM Repair is already running. Script will end."
		  Exit 1
		}
		Else
		{
		  Write-Host "[INFO] Connect to the WMI Namespace on $strComputer."
   		  $SMSCli = [wmiclass] $SMSCli = [wmiclass] "\\$strComputer\root\ccm:sms_client"
		  Write-Host "[INFO] Trigger the SCCM Repair on $strComputer."
		  # The actual repair is put in a variable, to trap unwanted output.
		  $repair = $SMSCli.RepairClient()
		  Write-Host "[INFO] Successfully connected to the WMI Namespace and triggered the SCCM Repair on $strComputer."
		  ########## START - PROCESS / PROGRESS CHECK AND RUN
		  # Comment the lines below if it is unwanted to wait for each repair to finish and trigger multiple repairs quickly.
		  Write-Host "[INFO] Wait (a maximum of 7 minutes) for the repair to actually finish."
		  For ($i = 0; $i -le 470; $i++)
		  {
			$checkProcess = Get-Process -Name ccmrepair* -ComputerName $strComputer
			Start-Sleep 1
			Write-Progress -Activity "Repairing client $strComputer ..." -Status "Repair running for $i seconds ..."
			
		    If ($checkProcess -eq $Null)
			{
			  Write-Host "[INFO] SCCM Client repair ran for $i seconds."
			  Write-Host "[INFO] SCCM Client repair process ran successfully on $strComputer."
			  Write-Host "[INFO] Check \\$strComputer\c$\Windows\SysWOW64\CCM\Logs\repair-msi%.log to make sure it was successful."
              $strComputer | Out-File -Filepath "C:\Users\cp559\Downloads\Automated_Repair_SCCM_Client\repairresults.txt" -NoClobber -Append
   			} 
			ElseIf ($i -eq 470)
			{
              $strComputer | Out-file -Filepath "C:\Users\cp559\Downloads\Automated_Repair_SCCM_Client\repairfails.txt" -NoClobber -Append
			  Write-Host "[ERROR] Repair ran for more than 7 minutes. Script will end and process will be stopped."
			  Invoke-Command -Computer $strComputer { Get-Process -Name ccmrepair* | Stop-Process -Force }
              
   			  Exit 1
			} 
		  }
		  ########## END - PROCESS / PROGRESS CHECK AND RUN

		}
	  }
	  Catch
	  {
		Write-Host "[WARNING] Either the WMI Namespace connect or the SCCM Repair trigger returned an error."
		Write-Host "[WARNING] This is most likely caused, because there is already a repair trigger running."
		Write-Host "[WARNING] Wait a couple of minutes and try again."
		# If the script keeps throwing errors, the WMI Namespace on $strComputer might be corrupt.
	  }
    }
  }
}
# RUN SCRIPT 
Repair_SCCM -SCRIPT_PARENT $SCRIPT_PARENT
#Finished

So, the author of this script came from vbScript with the Hungarian Notation (strComputer, arrComputer) and is also written like a vbScript, not a Powershell-y script. In Powershell, while you can write to files, you typically want objects because you can work with those objects to export them to a csv or even pipe them right to another function or cmdlet to perform another fix.

Write-Host should almost never be used. Write-Verbose gives you the flexibility to see the verbose messages or to simply remove the switch to run silently.

Start with something that is more Powershell driven, like this:

function Invoke-CMClientRepair {
    [CmdLetBinding()]
    param (
       [string[]]$ComputerName = $env:COMPUTERNAME,
       [int]$TimeOut = 470
    )
    begin {

    }
    process {
      $results = foreach ($computer In $ComputerName) {
	    #Put an asterisk (*) in front of the lines that need to be skipped in the input file.
        if ($computer.substring(0,1) -ne "*") {

	      Write-Verbose "[INFO] Starting trigger script for $computer."

	      try {

		    $getProcess = Get-Process -Name ccmrepair* -ComputerName $computer

		    if ($getProcess) {
		        Write-Verbose "[WARNING] SCCM Repair is already running. Script will end."
		        Exit 1
		    }
		    else {
                Write-Verbose "[INFO] Connect to the WMI Namespace on $computer."
                $SMSCli = [wmiclass] $SMSCli = [wmiclass]"\\$computer\root\ccm:sms_client"
                Write-Verbose "[INFO] Trigger the SCCM Repair on $computer."
                # The actual repair is put in a variable, to trap unwanted output.
                $repair = $SMSCli.RepairClient()
                Write-Verbose "[INFO] Successfully connected to the WMI Namespace and triggered the SCCM Repair on $computer."
                ########## START - PROCESS / PROGRESS CHECK AND RUN
                # Comment the lines below if it is unwanted to wait for each repair to finish and trigger multiple repairs quickly.
                Write-Verbose "[INFO] Wait (a maximum of 7 minutes) for the repair to actually finish."
                for ($i = 0; $i -le $TimeOut; $i++) {
	                $checkProcess = Get-Process -Name ccmrepair* -ComputerName $computer
	                Start-Sleep -Seconds 1
	                Write-Progress -Activity "Repairing client $computer ..." -Status "Repair running for $i seconds ..."
			
	                if ($checkProcess -eq $Null) {
		                Write-Verbose "[INFO] SCCM Client repair ran for $i seconds."
		                Write-Verbose "[INFO] SCCM Client repair process ran successfully on $computer."
		                Write-Verbose "[INFO] Check \\$computer\c$\Windows\SysWOW64\CCM\Logs\repair-msi%.log to make sure it was successful."
                        New-Object -TypeName PSObject -Property @{
                            Name = $Computer
                            Status = "Success"
                            RepairTime = $i
                        }

   	                } 
	                ElseIf ($i -eq $TimeOut) {
                        
		                Write-Verbose "[ERROR] Repair ran for more than 7 minutes. Script will end and process will be stopped."
		                Invoke-Command -Computer $computer { Get-Process -Name ccmrepair* | Stop-Process -Force }
                        
                        New-Object -TypeName PSObject -Property @{
                            Name = $Computer
                            Status = "Failed - Timeout Reached"
                            RepairTime = $i
                        }
	                } 
                }
                ########## END - PROCESS / PROGRESS CHECK AND RUN

		    }
	      }
	      Catch
	      {
		    Write-Verbose "[WARNING] Either the WMI Namespace connect or the SCCM Repair trigger returned an error."
		    Write-Verbose "[WARNING] This is most likely caused, because there is already a repair trigger running."
		    Write-Verbose "[WARNING] Wait a couple of minutes and try again."
		    # if the script keeps throwing errors, the WMI Namespace on $computer might be corrupt.
            New-Object -TypeName PSObject -Property @{
                Name = $Computer
                Status = ("Failed - {0}" -f $_)
                RepairTime = $null
            }
	      }
        }
      }
    }
    end {
        $results
    }
}


$computers = Get-Content C:\Scripts\Computers.txt
Invoke-CMClientRepair -Verbose #-ComputerName $computers

Output:

Invoke-CMClientRepair -Verbose

VERBOSE: [INFO] Starting trigger script for MY-PC.
VERBOSE: [INFO] Connect to the WMI Namespace on MY-PC.
VERBOSE: [WARNING] Either the WMI Namespace connect or the SCCM Repair trigger returned an error.
VERBOSE: [WARNING] This is most likely caused, because there is already a repair trigger running.
VERBOSE: [WARNING] Wait a couple of minutes and try again.

Status                                                                                                                                RepairTime Name  
------                                                                                                                                ---------- ----  
Failed - Cannot convert value "\\MY-PC\root\ccm:sms_client" to type "System.Management.ManagementClass". Error: "Invalid namespace "            MY-PC



PS C:\WINDOWS\system32> Invoke-CMClientRepair

Status                                                                                                                                RepairTime Name  
------                                                                                                                                ---------- ----  
Failed - Cannot convert value "\\MY-PC\root\ccm:sms_client" to type "System.Management.ManagementClass". Error: "Invalid namespace "            MY-PC

Once you get an object, you could do something like this to export the results:

$computers = Get-Content C:\Scripts\Computers.txt
$results = Invoke-CMClientRepair -Verbose #-ComputerName $computers
$results | Export-CSV -Path C:\Scripts\Results.csv -NoTypeInformation

Hi Rob/Sam,

Wow thank you, i really appreciate the effort you went into there to convert that into a powershell-y script!

A couple of odd things, once i run the script against a single PC, no matter what i change in the input.txt, it holds the previous computer name? even though it’s not hard coded anywhere, or even in the input.txt. Would this also run through a list of computers?
As i will most likely have 20-30 to do at a time! Also, it tends to record the failures, but not the successes? Many thanks guys

function Invoke-CMClientRepair {
    [CmdLetBinding()]
    param (
       [string[]]$ComputerName = $env:COMPUTERNAME,
       [int]$TimeOut = 470
    )
    begin {

    }
    process {
      $results = foreach ($computer In $ComputerName) {
	    #Put an asterisk (*) in front of the lines that need to be skipped in the input file.
        if ($computer.substring(0,1) -ne "*") {

	      Write-Verbose "[INFO] Starting trigger script for $computer."

	      try {

		    $getProcess = Get-Process -Name ccmrepair* -ComputerName $computer

		    if ($getProcess) {
		        Write-Verbose "[WARNING] SCCM Repair is already running. Script will end."
		        Exit 1
		    }
		    else {
                Write-Verbose "[INFO] Connect to the WMI Namespace on $computer."
                $SMSCli = [wmiclass] $SMSCli = [wmiclass]"\\$computer\root\ccm:sms_client"
                Write-Verbose "[INFO] Trigger the SCCM Repair on $computer."
                # The actual repair is put in a variable, to trap unwanted output.
                $repair = $SMSCli.RepairClient()
                Write-Verbose "[INFO] Successfully connected to the WMI Namespace and triggered the SCCM Repair on $computer."
                ########## START - PROCESS / PROGRESS CHECK AND RUN
                # Comment the lines below if it is unwanted to wait for each repair to finish and trigger multiple repairs quickly.
                Write-Verbose "[INFO] Wait (a maximum of 7 minutes) for the repair to actually finish."
                for ($i = 0; $i -le $TimeOut; $i++) {
	                $checkProcess = Get-Process -Name ccmrepair* -ComputerName $computer
	                Start-Sleep -Seconds 1
	                Write-Progress -Activity "Repairing client $computer ..." -Status "Repair running for $i seconds ..."
			
	                if ($checkProcess -eq $Null) {
		                Write-Verbose "[INFO] SCCM Client repair ran for $i seconds."
		                Write-Verbose "[INFO] SCCM Client repair process ran successfully on $computer."
		                Write-Verbose "[INFO] Check \\$computer\c$\Windows\SysWOW64\CCM\Logs\repair-msi%.log to make sure it was successful."
                        New-Object -TypeName PSObject -Property @{
                            Name = $Computer
                            Status = "Success"
                            RepairTime = $i
                        }

   	                } 
	                ElseIf ($i -eq $TimeOut) {
                        
		                Write-Verbose "[ERROR] Repair ran for more than 7 minutes. Script will end and process will be stopped."
		                Invoke-Command -Computer $computer { Get-Process -Name ccmrepair* | Stop-Process -Force }
                        
                        New-Object -TypeName PSObject -Property @{
                            Name = $Computer
                            Status = "Failed - Timeout Reached"
                            RepairTime = $i
                        }
	                } 
                }
                ########## END - PROCESS / PROGRESS CHECK AND RUN

		    }
	      }
	      Catch
	      {
		    Write-Verbose "[WARNING] Either the WMI Namespace connect or the SCCM Repair trigger returned an error."
		    Write-Verbose "[WARNING] This is most likely caused, because there is already a repair trigger running."
		    Write-Verbose "[WARNING] Wait a couple of minutes and try again."
		    # if the script keeps throwing errors, the WMI Namespace on $computer might be corrupt.
            New-Object -TypeName PSObject -Property @{
                Name = $Computer
                Status = ("Failed - {0}" -f $_)
                RepairTime = $null
            }
	      }
        }
      }
    }
    end {
        $results
    }
}


$computers = Get-Content C:\Users\cp559\Downloads\Automated_Repair_SCCM_Client\input.txt
$results = Invoke-CMClientRepair -Verbose #-ComputerName $computers
$results | Export-Csv -Path C:\Users\cp559\Downloads\Automated_Repair_SCCM_Client\Results.csv -NoTypeInformation

output

VERBOSE: [INFO] Starting trigger script for 5CG8201RWF.
VERBOSE: [INFO] Connect to the WMI Namespace on 5CG8201RWF.
VERBOSE: [INFO] Trigger the SCCM Repair on 5CG8201RWF.
VERBOSE: [WARNING] Either the WMI Namespace connect or the SCCM Repair trigger returned an error.
VERBOSE: [WARNING] This is most likely caused, because there is already a repair trigger running.
VERBOSE: [WARNING] Wait a couple of minutes and try again.

The function is setup by default to use the local system. You need to un-remark the -ComputerName switch to process the computers from the text file

$computers = Get-Content C:\Users\cp559\Downloads\Automated_Repair_SCCM_Client\input.txt
$results = Invoke-CMClientRepair -Verbose -ComputerName $computers
$results | Export-Csv -Path C:\Users\cp559\Downloads\Automated_Repair_SCCM_Client\Results.csv -NoTypeInformation

Thanks Rob - I should of read the whole script before i came back, my fault. Appreciate the help guys!