Link to home
Start Free TrialLog in
Avatar of Ron Shorts
Ron ShortsFlag for United States of America

asked on

Powershell script, help with for loop and running sccm advertisement remotely

Hi Experts,

I've been working on this powershell script and need some help with a couple of things.  The script will be called from this vbscript to pass the computername or listofcomputers variables.

What I'd like expertise or help with is:

1. I've tested calling this from the VBScript Code in top comments section, but didn't get the computername variable to pass down.  (this would be done from the vbscript above, but wanted just a test wrapper to test).  EDIT:  If this is too difficult, I'd like to set it up to pull from a list of computers.

2. I'm first running "Test-MyConnection" (if) the computer is online, I'm then calling a executable.exe.  This is the part I'm having issues with - I don't necessarily want to run a .exe here, but call an SCCM advert (pasted below from client center), or, I also have a vbscript that runs the advert I could call out to.  This advert would exist locally on every computer.  The part I'm unsure about, or how to code is having this advert run on each computer in the list of variables


SCCM Advert Code:
 get-wmiobject -query "SELECT * FROM CCM_Scheduler_ScheduledMessage WHERE ScheduledMessageID='SCCM00001-SCCM00002-5D2960FE'" -namespace "ROOT\ccm\policy\machine\actualconfig"
 $a=([wmi]"ROOT\ccm\policy\machine\actualconfig:CCM_SoftwareDistribution.ADV_AdvertisementID='SCCM00001',PKG_PackageID='SCCM00002',PRG_ProgramID='executable'");$a.ADV_RepeatRunBehavior='RerunAlways';$a.Put()
 $a=([wmi]"ROOT\ccm\policy\machine\actualconfig:CCM_SoftwareDistribution.ADV_AdvertisementID='SCCM00001',PKG_PackageID='SCCM00002',PRG_ProgramID='executable'");$a.ADV_RepeatRunBehavior='RerunAlways';$a.Put()
 $a=([wmi]"ROOT\ccm\policy\machine\actualconfig:CCM_SoftwareDistribution.ADV_AdvertisementID='SCCM00001',PKG_PackageID='SCCM00002',PRG_ProgramID='executable'");$a.ADV_MandatoryAssignments=$True;$a.Put()
 $a=([wmi]"ROOT\ccm\policy\machine\actualconfig:CCM_SoftwareDistribution.ADV_AdvertisementID='SCCM00001',PKG_PackageID='SCCM00002',PRG_ProgramID='executable'");$a.ADV_MandatoryAssignments=$True;$a.Put()
 $a=([wmi]"ROOT\ccm\policy\machine\actualconfig:CCM_SoftwareDistribution.ADV_AdvertisementID='SCCM00001',PKG_PackageID='SCCM00002',PRG_ProgramID='executable'");$a.PRG_Requirements='<?xml version=''1.0'' ?><SWDReserved>    <PackageHashVersion>4</PackageHashVersion>    <PackageHash.1></PackageHash.1>    <PackageHash.2>12497254D45F9E9FBCB1249D5D3CB463912AAF5BF9FA7A6D7428D742DC1D7885</PackageHash.2>    <NewPackageHash><Hash HashPreference="4" Algorithm="140789027962884" HashString="12497254D45F9E9FBCB1249D5D3CB463912AAF5BF9FA7A6D7428D742DC1D7885" SignatureHash="55F8A767E82821593F124ECFA375139FE56DD213F6D80A68A8AD377AADA9EF0B"/></NewPackageHash>    <ProductCode></ProductCode>    <DisableMomAlerts>false</DisableMomAlerts>    <RaiseMomAlertOnFailure>false</RaiseMomAlertOnFailure>    <BalloonRemindersRequired>false</BalloonRemindersRequired>    <PersistOnWriteFilterDevices>true</PersistOnWriteFilterDevices>    <DefaultProgram>false</DefaultProgram>    <PersistInCache>0</PersistInCache>    <DistributeOnDemand>false</DistributeOnDemand>    <Multicast>false</Multicast>    <MulticastOnly>false</MulticastOnly>    <MulticastEncrypt>false</MulticastEncrypt>    <DonotFallback>true</DonotFallback>    <PeerCaching>true</PeerCaching>    <OptionalPreDownload>false</OptionalPreDownload>    <PreDownloadRule></PreDownloadRule>    <Requirements></Requirements>    <AssignmentID></AssignmentID>    <ScheduledMessageID>SCCM00001-SCCM00002-5D2960FE</ScheduledMessageID>    <OverrideServiceWindows>TRUE</OverrideServiceWindows>    <RebootOutsideOfServiceWindows>FALSE</RebootOutsideOfServiceWindows>    <WoLEnabled>FALSE</WoLEnabled></SWDReserved>';$a.Put()
 $a=([wmi]"ROOT\ccm\policy\machine\actualconfig:CCM_SoftwareDistribution.ADV_AdvertisementID='SCCM00001',PKG_PackageID='SCCM00002',PRG_ProgramID='executable'");$a.PRG_Requirements='<?xml version=''1.0'' ?><SWDReserved>    <PackageHashVersion>4</PackageHashVersion>    <PackageHash.1></PackageHash.1>    <PackageHash.2>12497254D45F9E9FBCB1249D5D3CB463912AAF5BF9FA7A6D7442DC1D7885</PackageHash.2>    <NewPackageHash><Hash HashPreference="4" Algorithm="140789027962884" HashString="1249725F9E9FBCB1249D5D3CB463912A25ETGAF5BF9FA7A6D7428D742DC1D7885" SignatureHash="55F8A767E8282154ECFA375139FE56DD213F6D80A68A8AD377AADA9EF0B"/></NewPackageHash>    <ProductCode></ProductCode>    <DisableMomAlerts>false</DisableMomAlerts>    <RaiseMomAlertOnFailure>false</RaiseMomAlertOnFailure>    <BalloonRemindersRequired>false</BalloonRemindersRequired>    <PersistOnWriteFilterDevices>true</PersistOnWriteFilterDevices>    <DefaultProgram>false</DefaultProgram>    <PersistInCache>0</PersistInCache>    <DistributeOnDemand>false</DistributeOnDemand>    <Multicast>false</Multicast>    <MulticastOnly>false</MulticastOnly>    <MulticastEncrypt>false</MulticastEncrypt>    <DonotFallback>true</DonotFallback>    <PeerCaching>true</PeerCaching>    <OptionalPreDownload>false</OptionalPreDownload>    <PreDownloadRule></PreDownloadRule>    <Requirements></Requirements>    <AssignmentID></AssignmentID>    <ScheduledMessageID>SCCM00001-SCCM00002-5D29FE</ScheduledMessageID>    <OverrideServiceWindows>TRUE</OverrideServiceWindows>    <RebootOutsideOfServiceWindows>FALSE</RebootOutsideOfServiceWindows>    <WoLEnabled>FALSE</WoLEnabled></SWDReserved>';$a.Put()
 ([wmiclass]'ROOT\ccm:SMS_Client').TriggerSchedule('SCCM00001-SCCM00002-5D2960FE')

Open in new window


Any help is greatly appreciated!
Avatar of Michael Pfister
Michael Pfister
Flag of Germany image

I just scanned your scripts. I believe it will be quite difficult to pass a list of computers with an unknown length to a PowerShell script via command line.
Writing everything to a temporary text file and importing it back in PowerShell might be a workable solution.

I've looked at the VBScript and it does a lot with direct SCCM SQL queries… I'd rather dig in to the PowerShell module for SCCM.
It can very likely doing everything you want to achive with just one script.
Avatar of Ron Shorts

ASKER

Thanks for look at it Michael, instead of calling it from the vbscript and passing the list, could you help me set it up to pull from a list of computers just within the powershelll script?  

Any examples you can point me to for running the SCCM advertisement remotely from the list?
After a short glance at the VB script: It's main purpose seems to be to get parameter values for your PowerShell script from a database.
This is also easily done in PowerShell.

Thus you should migrate your VB script to PowerShell. Cause then you can hand over arrays as parameter or use pipelining.
Nice script to remotely rerun an advertisement for a package or application:
http://www.christopherkibble.com/using-powershell-rerun-sccm-client-advertisement/

You can wrap this i.e. with a loop and pull the computer names from a text file:

$ComputerList  = Get-Content .\computerlist,.txt
$ComputerList | foreach { Start-CCMRerunAdvertisement -ComputerName $_ -AdvertisementID Your AdvertisementID }

Open in new window

Or grab the computer list from a collection:

Get-CMDevice -CollectionName <String> | foreach ….
Michael, in pulling from a list, I would want it to grab the list of each computer the run through the entire sequence:

1. Function Test-Myconnection to test connectivity, then run the advertisement

2. The move to Test-Running, where it checks the processes, then when this exits, issue the restart-computer command

Questions:  

Where would I set this up in the script?  

Would this cycle through each computer in the list and wait until finished before moving to the next computer?  If so, how could I set this up to kick off the sequence for the first computer, then move on to the rest of the computers in the list?

I'm getting this error with the code you have, any ideas?

Start-CCMRerunAdvertisement : The term 'Start-CCMRerunAdvertisement' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included,
verify that the path is correct and try again.
At c:\test.ps1:52 char:27
+ $ComputerList | foreach { Start-CCMRerunAdvertisement -ComputerName $_ -Advertis ...
+                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (Start-CCMRerunAdvertisement:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException


Thank you
Can't test here right now but this should give you the basic idea:

Put the linked PowerShell Code at the beginning of your script
Function Start-CCMRerunAdvertisement { …
:
}

workflow foreachrerun {
   param([string[]]$computers)
   foreach –parallel ($computer in $computers) {
    Start-CCMRerunAdvertisement –ComputerName $Computer -AdvertisementID ….
}
}

Open in new window


Then run
foreachrerun <list of computers>


HTH
Michael, when you have time, would you mind looking at what I have here to check if this looks right?  https://pastebin.com/0niiTKUE

Thanks, Ron
<#

*******************************************************************************************************************************
** All code is for demonstration only and should be used at your own risk. I cannot accept liability for unexpected results. **
*******************************************************************************************************************************

Use: You're welcome to use, modify, and distribute this script.  I'd love to hear about how you're using it or 
modifications you've made in the comments section of the original post over at ChristopherKibble.com.

#>

Function Start-CCMRerunAdvertisement {
    
    <#
        .SYNOPSIS
            Restarts an SCCM advertisement on a remote computer.
        .DESCRIPTION
            This script will remotely connect to a computer running the CCM Client,
            find an advertisement by the Advertisement ID or the Package ID (or
            both), and then restart the advertisement.
        .NOTES
            File Name  : Start-CCMRerunAdvertisement.ps1
            Version    : 2017-09-21 (v1.0)
            Author     : Christopher Kibble (www.ChristopherKibble.com)
            Tested     : PowerShell Version 5
        .PARAMETER ComputerName
            The name of the remote computer to connect to.
        .PARAMETER AdvertisementID
            All or part of the Advertisement ID to run.  Wildcards accepted.
            Defaults to *.  Either this or PackageID must be specified.
        .PARAMETER PackageID
            All or part of the Package ID to run.  Wildcards accepted.
            Defaults to *.  Either this or AdvertisementID must be specified.
        .PARAMETER MaxRun
            If more than one advertisement meets your criteria, how many of the
            advertisements to run.  Defaults to 1.
        .PARAMETER MoreThanPing
            In environments where ICMP may not allow pinging a remote computer,
            this switch will make the script attempt to connect to C$ on the remote
            computer in order to determine if it's online.
        .EXAMPLE
            Start-CCMRerunAdvertisement -ComputerName SANDIAGO-001 -AdvertisementID "US000001"
    #>

    [CmdLetBinding()]Param(
        [Parameter(Mandatory=$true)][string]$computerName,
        [Parameter(Mandatory=$false)][string]$advertisementId = "*",
        [Parameter(Mandatory=$false)][string]$packageId = "*",
        [Parameter(Mandatory=$false)][int]$maxRun = 1,
        [Parameters(Mandatory=$false)][switch]$moreThanPing = $false
    )

    # TODO LIST:
    #
    #    - Better error control when WMI connections fail.
    #    - Are we using the best method to sort when using MaxRun?
    #

    if($advertisementId -eq "*" -and $packageId -eq "*") {
        Write-Error "You must supply either an AdvertisementID or a PackageID"
        return "Missing Parameters"
        break
    }

    $searchString = "$advertisementId-$packageId-*" 

    if(!(Test-Connection -ComputerName $computername -ErrorAction SilentlyContinue)) {
    
        if($moreThanPing) { 
            if(!(Get-ChildItem "\\$computername\c$" -ErrorAction SilentlyContinue)) {
                Write-Error "System Offline"
                Return "System Offline"
                break
            }
        } else {
            Return "System Offline"
            break
        }

    }

    Write-Verbose "Getting ID of ScheduleMessage on $computername"

    $schMsgs = Get-WmiObject -ComputerName $computername -Namespace "root\ccm\policy\machine\actualconfig" -Class CCM_Scheduler_ScheduledMessage

    $thisMsg = $schMsgs | ? { $_.ScheduledMessageID -like $searchString } | Sort ActiveTime -Descending | select -First $maxRun

    if(!$thisMsg) {
        Write-Verbose "Cannot Find Advertisement/Package on Target Computer"
        Return "Cannot Find Advertisment"
        break
    }

    $thisMsg | % {

        [xml]$activeMessage = $_.activeMessage

        $amProgramId = $activeMessage.SoftwareDeploymentMessage.ProgramID
        $amAdvId = $activeMessage.SoftwareDeploymentMessage.AdvertisementID
        $amPkgId = $activeMessage.SoftwareDeploymentMessage.PackageID
        $ScheduledMessageId = $_.ScheduledMessageId

        Write-Verbose  "Restarting $amArogramId (ADV=$amAdvId) (PKG=$amPkgId) for Schedule Message $ScheduledMessageId"

        $softwareDist = Get-WmiObject -ComputerName $computername -Namespace "root\ccm\policy\machine\actualconfig" -Class CCM_SoftwareDistribution -Filter "ADV_AdvertisementID = '$amAdvId' and PKG_PackageID = '$amPkgId'"

        $original_Rerun = $softwareDist.ADV_RepeatRunBehavior

        if($original_Rerun -ne "RerunAlways") {
            write-verbose "Changing Rerun Status from $original_Rerun to RerunAlways"
            $softwareDist.ADV_RepeatRunBehavior = "RerunAlways"
            $softwareDist.put() | Out-Null
        }

        Write-Verbose "Triggering Schedule on $computername"
        Invoke-WmiMethod -ComputerName $computername -Namespace "root\ccm" -Class "SMS_CLIENT" -Name TriggerSchedule $ScheduledMessageId | Out-Null
        
        Write-Verbose "Sleeping for 5 seconds"
        Start-Sleep -Seconds 5

        if($original_Rerun -ne "RerunAlways") {
            Write-Verbose "Changing Rerun Status back to $original_Rerun"
            $softwareDist.ADV_RepeatRunBehavior = "$original_Rerun"
            $softwareDist.put() | Out-Null
        }

        Return "Reran Advertisement"
   
    }

}


function Send-Email {
   # param (
        [Parameter(Mandatory=$true)]
        [String]$Body,
        [String]$ComputerName
   # )
    Send-MailMessage -From “admin@domain.com" -To “user@domain.com" -SMTPServer mail.domain.com -Subject “$($computername) is OFFLINE  " # -Body $Body

}

function Test-Running {
    param (
        [Parameter(Mandatory=$true)]
        [System.String]$ComputerName
    )
$processList = @'
	"Name",				"Expected",		"Running"
	"cmd",				"1",			"0"
	"powershell",	"1",			"0"
	"OfficeClickToRun",	"2",			"0"
'@ | ConvertFrom-Csv | ForEach-Object {$_.Expected = [int]$_.Expected; $_}
    $splat = @{}
    $splat['ComputerName'] = $computerName
    Write-Host "Testing processes on $($computerName)" -ForegroundColor Yellow
    Do  {
      $processList | ForEach-Object {
          $_.Running = @(Get-Process $_.Name @splat -ErrorAction SilentlyContinue).Count
      }
      ($processList | Format-Table -AutoSize | Out-String).Split("`r`n", [StringSplitOptions]::RemoveEmptyEntries) | Write-Host
      If ($running = @($processList | Where-Object {$_.Running -ge $_.Expected}).Count) {
          Start-Sleep -Seconds 5
      }
    } Until (-not $running)

 Restart-Computer -Computer $Computername -Force

    return 0
} 

function Test-MyConnection {
    param (
        [Parameter(Mandatory=$true)]
        [System.String]
        $ComputerName
    )
    if (Test-Connection $computername -Quiet -Count 2) {
        Write-Host "Ping successful on $($computerName)" -ForegroundColor Green
        Test-Running -ComputerName $ComputerName
    }
    else {
        send-email $computername -ComputerName $computername -body $processlistOP
        Write-Host $($computerName) "is Offline." -ForegroundColor Red  
    }
  }   

  

workflow foreachrerun {
    param([string[]]$computers)
    foreach –parallel ($computer in $computers) {
       Start-CCMRerunAdvertisement –ComputerName $Computer -AdvertisementID UNC53487
    }
}
$ComputerList  = Get-Content "c:\computerlist.txt"
foreachrerun -Computers $ComputerList

Open in new window


I didn't use the other functions like Test-Connection. This has not been tested, currently I have  no access to a System Center environment.
@Michael - I ran the sccm powershell script from the site, and got the same error.  I found this one which worked for me when I put in the remote computer name:  https://marco-difeo.de/2011/10/13/powershell-sccm-readvertise-a-previously-installed-softwarepackage-remotly/  

I put this into a script here:  https://pastebin.com/52LWwZZH

Here's what I need help with if this can be done:

Putting the first part of the script (lines 1 - 111) into a function. Then run this at (line 164) in the sequence.

Build a for loop to pull computer names from a text file to cycle through (currently line 15 just for one computer).

Run the other functions and sequence in this order from the list of computers:

a) Test-MyConnection (line 155)

b) Function from step 1 above (SCCM advertisement)

c) Test-Running (already built into Test-MyConnection function) to monitor processes count.

Also wanted to see if running a list like this, it would need to wait for each computer to finish, or it could execute the functions and move to the next.

Many thanks!
Michael, if the above can't be done, I did get Christopher Kibble's script to work as a standalone... here's what I had to do...

Remark out this line:  # [Parameters(Mandatory=$false)][switch]$moreThanPing = $false  # This line was causing the error

Put this in the bottom of Christopher's script:  Start-CCMRerunAdvertisement -ComputerName SANDIAGO-001 -AdvertisementID "US000001"

-or-

Ran this from another powershell shell:   powershell -Command "& 'C:\Start-CCMRerunAdvertisement.ps1' 'Start-CCMRerunAdvertisement' '-ComputerName' '"SANDIAGO-001" -AdvertisementID "US000001"'"  

Could you help me with the logic on this to pull from a list of computers and sequence the other functions I have please?
No better idea right now for parallel processing than splitting it in 2 pieces...

RerunAdvertisement.ps1 wraps all the stuff you want for a single PC. You can test it by starting .\RerunAdvertisement.ps1 "computername" for a system

To run it in parallel use this:

$ComputerList  = Get-Content "c:\computerlist.txt"
foreach ($Computer in $ComputerList) {
    $running = @(Get-Job | Where-Object { $_.State -eq 'Running' })
    if ($running.Count -le 8) {
         Start-Job  -filepath .\rerunadvertisement.ps1 -ArgumentList $Computer
     } else {
         $running | Wait-Job
     }
    Get-Job | Receive-Job
}
  }

Open in new window


HTH
Can you give an example of the command line arguments I'd need to pass?  Tried this:
rerunadvertisement.ps1" Start-CCMRerunAdvertisement -Computername $Computer -AdvertisementID "XXX00001"
Getting this error:
Start-Job : Cannot bind parameter 'InitializationScript'. Cannot convert the "Start-CCMRerunAdvertisement" value of type "System.String" to type "System.Management.Automation.ScriptBlock".

It's getting hung up right where the -Argument list starts.  Would I need to modify rerunadvertisement.ps1?
Oh great ... I didn't realize the upload isn't there  .. sorry.
I'm not in the office where the original file is so I tried to remember...

Save the attachment to a folder and test it:

.\RerunAdvertisement.ps1 -ComputerName xxxx -AdvertisementID YYYZZZZ

If its ok you can use the wrapper 2 posts above, put it in a 2nd PowerShell script to run it in parallel.

HTH
RerunAdvertisement.ps1
No problem, thanks.  So this works perfectly using the command line above.  

The wrapper I can't seem to get to work, here is exactly what I'm using, minus the actual AdvertisementID:

$ComputerList  = Get-Content "c:\computerlist.txt"
foreach ($Computer in $ComputerList) {
    $running = @(Get-Job | Where-Object { $_.State -eq 'Running' })
    if ($running.Count -le 8) {
         Start-Job  -filepath .\rerunadvertisement.ps1 -ArgumentList $Computer -AdvertisementID "YYYZZZZ"
     } else {
         $running | Wait-Job
     }
    Get-Job | Receive-Job
}

Open in new window

Can't test here but the line should read

 
Start-Job  -filepath .\rerunadvertisement.ps1 -ComputerName $Computer -AdvertisementID "YYYZZZZ"

Open in new window

Hmmm.. that what I had, it's not recognizing the 'ComputerName' parameter?

Start-Job : A parameter cannot be found that matches parameter name 'ComputerName'.
At C:\Wrapper-RerunAdvertisement.ps1:6 char:57
+           Start-Job  -filepath .\rerunadvertisement.ps1 -ComputerName $Computer  ...
+                                                         ~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Start-Job], ParameterBindingException
    + FullyQualifiedErrorId : NamedParameterNotFound,Microsoft.PowerShell.Commands.StartJobCommand
ASKER CERTIFIED SOLUTION
Avatar of Michael Pfister
Michael Pfister
Flag of Germany image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Michael, works fantastic, much appreciated.

One other thing, as ste5an mentioned, if I want to grab an array of computer names from a vbscript like this:
https://pastebin.com/ip1RSZxC, and pass the computernames into this script instead of from a list, would this be too difficult, or would it be more efficient to just re-write the vbscript into powershell?  

Thanks again
Sorry, must have missed your comment.
Passing an unknown number of elements is difficult

You could:
a) call the PowerShell script with the computer name as parameter from your VBScript for each computer (ok, no pararell processing here)
b) let the VBScript write the computernames to a text file and import the file to the PowerShell script
c) as you said rewrite the VBScipt in PowerShell.

In case you're choosing c) have a look at the great possibilities the SCCM PowerShell module has to offer so you don't have to struggle with direct queries to the SCCM database.
Thanks Michael.  So I did evolve this to use inline processing but I'm still having a couple of issues I was wondering if you could help with.

This works how I need it to:  run the sccm advertisement on remote computers from a list and monitors the processes, then reboots.

However, this advertisement needs to be run multiple times on each computer and multiple reboots.  

PROBLEM:  I have this sending an email and writing to a CSV file each reboot.  The emails are too much to keep track of.  The CSV is unable to be viewed during the time it's being logged to.

Is there a way to have the emails sent to all of the computers in the input list only and not after each run?

Is there a way to have the logging done to the CSV without disrupting if the script is running?

Finally, not sure if this is possible, but is there a way to have this whole script process 5 times after reboots for each computer in the input computerlist.txt?

Thanks for all of your help!

$OutArray = @()
workflow foreachrerun {
    param([string[]]$computers)
    foreach –parallel ($computer in $computers) {
       InlineScript {

Function Start-CCMRerunAdvertisement {

    [CmdLetBinding()]Param(
        [Parameter(Mandatory=$true)][string]$computerName,
        [Parameter(Mandatory=$false)][string]$advertisementId = "*",
        [Parameter(Mandatory=$false)][string]$packageId = "*",
        [Parameter(Mandatory=$false)][int]$maxRun = 1
    )

    if($advertisementId -eq "*" -and $packageId -eq "*") {
        Write-Error "You must supply either an AdvertisementID or a PackageID"
        return "Missing Parameters"
        break
    }

    $searchString = "$advertisementId-$packageId-*" 

    if(!(Test-Connection -ComputerName $computername -ErrorAction SilentlyContinue)) {

        if($moreThanPing) { 
            if(!(Get-ChildItem "\\$computername\c$" -ErrorAction SilentlyContinue)) {
                Write-Error "System Offline"
                Return "System Offline"
                break
            }
        } else {
            Return "$Computername is Offline"
            break
        }

    }

    Write-Verbose "Getting ID of ScheduleMessage on $computername"

    $schMsgs = Get-WmiObject -ComputerName $computername -Namespace "root\ccm\policy\machine\actualconfig" -Class CCM_Scheduler_ScheduledMessage

    $thisMsg = $schMsgs | ? { $_.ScheduledMessageID -like $searchString } | Sort ActiveTime -Descending | select -First $maxRun

    if(!$thisMsg) {
        Write-Verbose "Cannot Find Advertisement/Package on Target Computer"
        Return "Cannot Find Advertisment"
        break
    }

    $thisMsg | % {

        [xml]$activeMessage = $_.activeMessage

        $amProgramId = $activeMessage.SoftwareDeploymentMessage.ProgramID
        $amAdvId = $activeMessage.SoftwareDeploymentMessage.AdvertisementID
        $amPkgId = $activeMessage.SoftwareDeploymentMessage.PackageID
        $ScheduledMessageId = $_.ScheduledMessageId

        Write-Verbose  "Restarting $amArogramId (ADV=$amAdvId) (PKG=$amPkgId) for Schedule Message $ScheduledMessageId"

        $softwareDist = Get-WmiObject -ComputerName $computername -Namespace "root\ccm\policy\machine\actualconfig" -Class CCM_SoftwareDistribution -Filter "ADV_AdvertisementID = '$amAdvId' and PKG_PackageID = '$amPkgId'"

        $original_Rerun = $softwareDist.ADV_RepeatRunBehavior

        if($original_Rerun -ne "RerunAlways") {
            write-verbose "Changing Rerun Status from $original_Rerun to RerunAlways"
            $softwareDist.ADV_RepeatRunBehavior = "RerunAlways"
            $softwareDist.put() | Out-Null
        }

        Write-Verbose "Triggering Schedule on $computername"
        Invoke-WmiMethod -ComputerName $computername -Namespace "root\ccm" -Class "SMS_CLIENT" -Name TriggerSchedule $ScheduledMessageId | Out-Null

        Write-Verbose "Sleeping for 5 seconds"
        Start-Sleep -Seconds 5

        if($original_Rerun -ne "RerunAlways") {
            Write-Verbose "Changing Rerun Status back to $original_Rerun"
            $softwareDist.ADV_RepeatRunBehavior = "$original_Rerun"
            $softwareDist.put() | Out-Null
        }

        Return "Kickit Advert Reran on succesfully on $computername"

    }

}

function Send-EmailOffline {
     Send-MailMessage -From “admin@domain.com" -To "user@domain.com" -SMTPServer mail.domain.com -Subject “$($computername) is Offline" -Body "$($ComputerName) is Offline, please power on and re-run script."
}

function Send-EmailComplete {
    Send-MailMessage -From “admin@domain.com" -To "user@domain.com" -SMTPServer mail.domain.com -Subject “$($computername) Script completed." -Body "$($ComputerName) Script completed and is rebooting."
}

function Test-MyConnection {
    param (
        [Parameter(Mandatory=$true)]
        [System.String]
        $ComputerName
    )
    if (Test-Connection $computername -Quiet -Count 2) {
        Write-Output "Ping successful on $($computerName)"      
        Test-Running -ComputerName $ComputerName 
    }
    else {
        Send-EmailOffline -ComputerName $computername   
    }
  }   

function Test-Running {
    param (
        [Parameter(Mandatory=$true)]
        [System.String]$ComputerName
    )
$processList = @'
	"Name",				"Expected",		"Running"
	"cmd",				"1",			"0"
	"powershell",	    "1",			"0"
	"OfficeClickToRun",	"2",			"0"
'@ | ConvertFrom-Csv | ForEach-Object {$_.Expected = [int]$_.Expected; $_}
    $splat = @{}
    $splat['ComputerName'] = $computerName

    Do  {
      $processList | ForEach-Object {
          $_.Running = @(Get-Process $_.Name @splat -ErrorAction SilentlyContinue).Count
      }

      ($processList | Format-Table -AutoSize | Out-String).Split("`r`n", [StringSplitOptions]::RemoveEmptyEntries) | Write-Output
      If ($running = @($processList | Where-Object {$_.Running -ge $_.Expected}).Count) {
          Start-Sleep -Seconds 5
      }
      Write-Output "Monitoring processes on $($computerName)" 
  
    } Until (-not $running)

    Send-EmailComplete -ComputerName $computername 

    # Array for logging 
    $Today = (Get-Date).ToString('MM-dd-yyyy'),
    $ExactTime = Get-Date -Format "MM-dd-yyyy HHmm tt"
    $Logfile = "C:\Temp\Script.csv"

    $myobj = "" | Select "Computer", "ExactTime"
    $myobj.Computer = $ComputerName
    $myobj.ExactTime = $($(Get-Date).ToString('yyyy-MM-dd::hh:mm:ss'))

    $OutArray += $myobj
    $OutArray | Export-Csv $Logfile -NoTypeInformation -Append 

    Restart-Computer -Computer $Computername -Force

    Return "Process count finished on $computername"

} 
            Start-CCMRerunAdvertisement –ComputerName $using:Computer -AdvertisementID "XXX00000"
            Test-MyConnection -ComputerName $using:Computer
        }
    }
}
$ComputerList  = Get-Content "C:\Temp\computerlist.txt"
foreachrerun -Computers $ComputerList

Open in new window

I have to ask: why isn't SCCM doing all this and you need an extra script?
Sure, this is for new builds, and to have less admin work through the client center to make sure all updates are installed.  The advertisement I'm running is a call to a local sccm package that triggers the schedule for all updates, scans, software assignments, etc.,  This installs updates, software for each, the reason for multiple reboots is due to cumulative updates during the process, or updates after software installations, etc.,
I usually put all that stuff in the task sequence for the OS deployment. Updates, reboots, etc.
When the task sequence ends the client is up to date.

And a cumulative update is a cumulative update. No need to install the older ones...