Gathering results into a .txt file

This topic contains 7 replies, has 3 voices, and was last updated by  Chris 4 weeks ago.

  • Author
    Posts
  • #102827

    Chris
    Participant

    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 🙂

  • #102830

    Sam Boutros
    Participant

    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")
    
    • #102841

      Chris
      Participant

      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.

    • #102851

      Sam Boutros
      Participant

      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
      
  • #102854

    Rob Simmers
    Participant

    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
    
  • #102895

    Chris
    Participant

    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.
    
    
  • #102908

    Rob Simmers
    Participant

    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
    
  • #102928

    Chris
    Participant

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

You must be logged in to reply to this topic.