Sidebar Menu

If a user switches off a computer during a task sequence deployment the task may have the status installing in Software Center. The only way to solve this issue is to reinstall the Configuration Manager Client. You can imagine that there is a more user-friendly way.

Description Picture
In SCCM there is a task sequence to deploy an OSD Upgrade package.
In Software Center the OSD Upgrade package is ready for installation:
During the deployment the computer is switched off. You see that in the logfile C:\WINDOWS\CCM\Logs\SMSTSLog\smsts.log
If the computer is started up again, you see that the status of the TS is still installing.

Use the WMI Explorer (or WBEMTEST) to go to the namespace ROOT\ccm\SoftMgmtAgent and open the class CCM_TSExecutionRequest. There is one instance and check the properties:

  • CompletionState
  • ContentID
  • MIFPackageName.
CliSpy gives the status complete.
Rerunning the task sequence from the Client Center for Configuration Manager makes no sense.
You can trigger the installation from a remote computer (management server) with the command  ."Repair-BrokenTaskSequence v01.ps1" -Computername <ComputerName> -PackageID <PackageID>​

Please keep in mind that the script is run in test mode by default. You have to specify the -ProductionRun switch to make changes. 

 

The code:

 

<#
.SYNOPSIS
    Reset a Task sequence that has failed but still in a installing state in software center.

.DESCRIPTION
    It can happen that a user switches off a computer during a Task Sequence deployment. The task sequence
    may be in an installing state in software center, instead of failed (or whatever). 
    This script resets the failing task sequence so that the task sequence can be restarted.

.EXAMPLE
     Resets the task sequence in test mode.
     ."\Repair-BrokenTaskSequence v03.ps1" -ComputerNames WINDOWS10DOMAIN1 -PackageID ATT0002C

.EXAMPLE
     Resets the task sequence in prodduction mode for both computers WINDOWS10DOMAIN1 and WINDOWS10DOMAIN2.
     ."\Repair-BrokenTaskSequence v03.ps1" -ComputerNames WINDOWS10DOMAIN1,WINDOWS10DOMAIN2 -PackageID ATT0002C -ProductionRun

.EXAMPLE
     Resets the task sequence in prodduction mode and logging to C:\Logs on the computer where the script is running from.
     ."\Repair-BrokenTaskSequence v03.ps1" -ComputerName WINDOWS10DOMAIN -PackageID ATT0002C -ProductionRun -LogPath C:\Logs

.EXAMPLE
     Read the content of the file 'computers.txt' and resets the task sequence in production mode for packageid ATT0002C and logging 
     to C:\Logs on the computer where the script is running from.
     ."\Repair-BrokenTaskSequence v03.ps1" -InputFile c:\temp\computers.txt -PackageID ATT0002C -ProductionRun -LogPath C:\Logs

.EXAMPLE
     Read the content of the file 'computers.txt' and resets the task sequence in production mode for any packageid and logging to 
     C:\Logs on the computer where the script is running from.
     ."\Repair-BrokenTaskSequence v03.ps1" -InputFile c:\temp\computers.txt -PackageID * -ProductionRun -LogPath C:\Logs

.NOTES
    Author:  Willem-Jan Vroom
    Website: https://www.vroom.cc/
    Twitter: @TheStingPilot

v0.1:
   * Initial version.

v0.2:
   * LogFile has been added.
   * Improved error handling.

v0.3:
   * The command line option 'ComputerName' has been replaced by 'ComputerNames'.
   * The command line option '-Inputfile' has been added.
   * PackageID can be a * for any packageid.

#>

[CmdLetBinding()]

param
(

  # Computername, leave empty for local computer.
  [Parameter(Mandatory=$False)]
  [string[]]   $ComputerNames = @(),

  # PackageID of the task sequence that needs to be fixed. You can use % for all task sequences
  [Parameter(Mandatory=$False)]
  [String]  $PackageID ="PI1006E1",

  #Specify ProductionRun. Default is false
  [Switch]  $ProductionRun,

  #Specify logpath for the log file.
  [String]  $LogPath = "",

  #Inputfile with all the computers. It is a text file with a computername on each line.
  [String]  $Inputfile = ""

)


# =============================================================================================================================================
# Function block
# =============================================================================================================================================

  Function Write-EntryToResultsFile
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       03-August-2018
    Created by:       Willem-Jan Vroom
    Organization:     
    Functionname:     Write-EntryToResultsFile
    =============================================================================================================================================
    .SYNOPSIS

    This function adds the success or failure information to the array that contains the log
    information.

    #>

    Param
     (
      [String]  $Result       = "",
      [String]  $Action       = "",
      [String]  $Message      = ""
     )

    $Record                = [ordered] @{"Timestamp"="";"Action" = "";"Result"= "";"Message"= ""}
    $Record."Timestamp"    = (Get-Date -UFormat "%a %e %b %Y %X").ToString()
    $Record."Result"       = $Result
    $Record."Action"       = $Action
    $Record."Message"      = $Message
    $objRecord             = New-Object PSObject -Property $Record
    $Global:arrTable      += $objRecord

    Write-Verbose ">>  Write-EntryToResultsFile" 
    Write-Verbose "--> Entry written to the logfile:"
    Write-Verbose "     Result        = $Result"
    Write-Verbose "     Action        = $Action"
    Write-Verbose "     Message       = $Message"
    Write-Verbose "#################################################################"

   }

  Function Export-ResultsLogFileToCSV
   {

    <#
    .NOTES
    =============================================================================================================================================
    Created with:     Windows PowerShell ISE
    Created on:       06-September-2018
    Created by:       Willem-Jan Vroom
    Organization:     
    Functionname:     Export-ResultsLogFileToCSV
    =============================================================================================================================================
    .SYNOPSIS

    This function writes the logfile content to a CSV file.

    #>

    If($Global:arrTable.Count -gt 0)
     {
      $Global:arrTable | Export-Csv $strCSVLogFileSucces -NoTypeInformation
      Write-Verbose ">> Export-ResultsLogFileToCSV: The file '$strCSVLogFileSucces' has been written." 
     } 
      else
     {
      Write-Error "Something went wrong while writing the logfile '$strCSVLogFileSucces'. Maybe nothing to report..." -Category CloseError 
     } 
   }

# =============================================================================================================================================
# End function block
# =============================================================================================================================================

# =============================================================================================================================================
# Define the variables
# =============================================================================================================================================

  Clear-Host

  $strNamespace     = "ROOT\ccm\SoftMgmtAgent"
  $strClassname     = "CCM_TSExecutionRequest"
  $Global:arrTable  = @()
  $valCounter       = 1
  $bolMaxComputers  = $False
  
  if($ComputerNames -eq $null -or $ComputerNames -eq ".")
   {
    $ComputerNames    = @($env:COMPUTERNAME)
   }

  if($LogPath -eq "")
   {
    $LogPath   = Split-Path -parent $MyInvocation.MyCommand.Definition
   }
    else
   {
    if(-not(Test-Path($LogPath)))
     {
      New-Item -Path $LogPath | Out-Null
      Write-Verbose "The folder '$LogPath' has been created."
     } 
   }

  Write-Verbose "The folder '$LogPath' is used for storing the logfile."

# =============================================================================================================================================
# Read the file with all the computer names
# =============================================================================================================================================
 
  if($Inputfile -ne "")
   {
    if(Test-Path($Inputfile))
     { 
      $ComputerNames = @(Get-Content -Path $Inputfile)
      Write-Verbose "The inputfile '$Inputfile' has been read."
      Write-Verbose "The following $($ComputerNames.Count) will be processed:"
      ForEach ($ComputerName in $ComputerNames)
       {
        Write-Verbose "Computername: $Computername."
       }
     }
      else
     {
      Write-Host "The file '$Inputfile' does not exists. Thus quitting."
      Exit 1
     }
   }
  
# =============================================================================================================================================
# Define the log file. This log file contains all the results.
# The filenam may contain maximum of 3 computer names. Otherwise the filename may be too long.
# =============================================================================================================================================

  $ComputerNameForFileName = ""
  ForEach ($word in $ComputerNames)
   {
    $ComputerNameForFileName = "$ComputerNameForFileName $word"
    if($valCounter -ge 3)
     {
      $bolMaxComputers = $true
      Break
     }
    $valCounter++
   }
 
  $ComputerNameForFileName = ($ComputerNameForFileName.Substring(1)).Replace(" ","_")

  If($bolMaxComputers)
   {
    $ComputerNameForFileName += " (PLUS $($ComputerNames.Count - $valCounter) MORE)"
   }

  $strLastPartOfFileName = " (" + (Get-Date).ToString('G') + ").csv"
  $strLastPartOfFileName = $strLastPartOfFileName -replace ":","-"
  $strLastPartOfFileName = $strLastPartOfFileName -replace "/","-"

  If(-not($ProductionRun))
   {
    $strLastPartOfFileName = " (RUNNING IN TEST MODE)" + $strLastPartOfFileName
   }

  $strCSVLogFileSucces   = $LogPath + "\"+ $ComputerNameForFileName + $strLastPartOfFileName

# =============================================================================================================================================
# Find all the arguments and put them in the log file
# Source: https://ss64.com/ps/psboundparameters.html
# =============================================================================================================================================

  Write-EntryToResultsFile -Result "Information" -Action "Used script" -Message $LogPath + "\" + $strCurrentFile

  ForEach ($boundparam in $PSBoundParameters.GetEnumerator()) 
   {
    Write-EntryToResultsFile -Result "Information" -Action "Key: $($boundparam.Key)" -Message "Value: $($boundparam.Value)"
   }

# =============================================================================================================================================
# Go through the list of computers
# =============================================================================================================================================

  $valCounter       = 1
  ForEach ($ComputerName in $ComputerNames)
   {

    # =============================================================================================================================================
    # Check if the computer can be reached
    # If so, continue.
    # =============================================================================================================================================
  
      Write-Progress -Activity "Checking all the computers for corrupt task sequences." -Status "Processing $ComputerName" -PercentComplete ($valCounter / $ComputerNames.Count * 100)

      Write-EntryToResultsFile -Result "" -Action "==================================================================================================" -Message ""

      Try
       {
        $bolReachable = Test-Connection -ComputerName $ComputerName -Quiet
        Write-EntryToResultsFile -Result "Information" -Action "Check online status of computer $ComputerName"
       }
        Catch
       {
        Write-EntryToResultsFile -Result "Error" -Action "Check online status of computer $ComputerName" -Message "The computer $ComputerName could not be reached."
        Continue
       }

      if($bolReachable)
       {
    
        # =============================================================================================================================================
        # Connect to namespace ROOT\ccm\SoftMgmtAgent and find the instances in CCM_TSExecutionRequest that have issues.
        # =============================================================================================================================================

        $objInstance = $null

        Write-EntryToResultsFile -Result "Information" -Action "Connecting to namespace $strNamespace and find all the instances in $strClassname on computer $ComputerName that have issues."
        Write-EntryToResultsFile -Result "Information" -Action " -CompletionState = Failure"
        Write-EntryToResultsFile -Result "Information" -Action " -PackageID       = $PackageID"
        
        Try
         {
          $objInstance         = Get-WmiObject -Namespace $strNamespace -Class $strClassname -ComputerName $ComputerName -ErrorAction SilentlyContinue -ErrorVariable DetailedError | Select-Object -Property * | Where-Object {$_.CompletionState -eq "Failure" -and $_.ContentID -like $PackageID}
         }
          Catch
         {
          Write-EntryToResultsFile -Result "Error" -Action "WMI error on computer $ComputerName." -Message "$($error[0].Exception). More details: $DetailedError." 
          Write-Verbose "WMI error on computer $ComputerName $($error[0].Exception). More details: $DetailedError."
          Continue
         }

        if($objInstance -ne $null)
         {

          # =============================================================================================================================================
          # Export all the properties to the log file. Always usefull.
          # =============================================================================================================================================
      
            Write-EntryToResultsFile -Result "Information" -Action "Connected with the following details:"
            Write-EntryToResultsFile -Result "Information" -Action "Computer:  $ComputerName"
            Write-EntryToResultsFile -Result "Information" -Action "Namespace: $strNamespace"
            Write-EntryToResultsFile -Result "Information" -Action "Class:     $strClassname"
            
            $arrProperties = ((Get-WmiObject -Namespace $strNamespace -Class $strClassname -List -ComputerName $ComputerName).Properties).Name
        
            ForEach ($objProperty in $arrProperties)
             {
              $value = (Get-WmiObject  -Namespace $strNamespace -Class $strClassname -Property $objProperty -ComputerName $ComputerName).$objProperty
              Write-EntryToResultsFile -Result "Information" -Action "Property '$objProperty' has value '$value'"
             }

          # =============================================================================================================================================
          # Remove the faulty instance.
          # =============================================================================================================================================
          
            $objInstance.Path | Remove-WmiObject -WhatIf:(-not($ProductionRun))
            Write-EntryToResultsFile -Result "Information" -Action "$($objInstance.Path) has been removed."

            $strNamespace = "ROOT\ccm"
            $strQuery     = "Select * from SMS_MaintenanceTaskRequests"
            Write-EntryToResultsFile -Result "Information" -Action "Running '$strQuery' on namespace $strNamespace on computer $ComputerName."
            $objWMI       = (Get-WmiObject -Namespace $strNamespace -Query $strQuery -ComputerName $ComputerName)
            $objWMI | Remove-WmiObject -WhatIf:(-not($ProductionRun))
            $arrWMIItems = @($objWMI)
            ForEach ($objWMIItem in $arrWMIItems)
             {
              Write-EntryToResultsFile -Result "Information" -Action "$($objWMIItem.__PATH) has been removed."
             }

            if($ProductionRun)
             {
              Get-Service -Name ccmexec -ComputerName $ComputerName | Restart-Service
              Write-EntryToResultsFile -Result "Information" -Action "The service ccmexec has been restarted on $ComputerName."
             }
           }
            else   
           {
            Write-EntryToResultsFile -Result "Information" -Action "Connected with the following details:"
            Write-EntryToResultsFile -Result "Information" -Action "Computer:  $ComputerName"
            Write-EntryToResultsFile -Result "Information" -Action "Namespace: $strNamespace"
            Write-EntryToResultsFile -Result "Information" -Action "Nothing done as there are no task sequences that need to be reset."
           } 
       }
        else
       {
        Write-EntryToResultsFile -Result "Error" -Action "No connection with remote computer $ComputerName."
       } 
       $valCounter++
   }

   Write-EntryToResultsFile -Result "" -Action "==================================================================================================" -Message ""

   Write-EntryToResultsFile -Result "Information" -Action "Script ended." 
   Export-ResultsLogFileToCSV 
   Exit 0

You can download the script

 

 

Attachments:
Download this file (Repair-BrokenTaskSequence v01.7z)Repair-BrokenTaskSequence v01[Repair-BrokenTaskSequence v01]2 kB
Download this file (Repair-BrokenTaskSequence v03.7z)Repair-BrokenTaskSequence v03[Repair-BrokenTaskSequence v03]3 kB