Avatar of Ron Shorts
Ron Shorts
Flag for United States of America asked on

Powershell Script - Increment Counter?

Hi Experts,

Looking for some expertise with this script.

Here is what the script does:

1) Reads from a text file with computer names and remotely runs a SCCM advertisement.

2) The advertisement opens some processes - which I have in a loop in this script.  

3) When the processes read 0 the loop is completed, then moves to the next command to RESTART the computer.

4) Is there any way to have this run 3 times, and then shutdown on the 3rd time?

$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
        #[Parameters(Mandatory=$false)][switch]$moreThanPing = $false
    )
    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 "Advert Reran on succesfully on $computername"
    }
}
function Send-EmailOffline {
    Send-MailMessage -From “admin@domain.com" -To "admin1@domain.com" -SMTPServer mail.domain.com -Subject “$($computername) is Offline" -Body "$($ComputerName) is Offline, please power on and re-run script."
}
function Send-EmailKickit {
    Send-MailMessage -From “admin@domain.com" -To "admin1@domain.com" -SMTPServer mail.domain.com -Subject “$($computername) Completed." -Body "$($ComputerName) 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"
    "TrustedInstaller", "1",            "0"
    "TIWorker",         "1",            "0"
    "OfficeClickToRun", "2",            "0"
'@ | ConvertFrom-Csv | ForEach-Object {$_.Expected = [int]$_.Expected; $_}
    $splat = @{}
    $splat['ComputerName'] = $computerName
    #Write-Output "Monitoring processes on $($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-EmailKickit -ComputerName $computername 
    $Today = (Get-Date).ToString('MM-dd-yyyy'),
    $ExactTime = Get-Date -Format "MM-dd-yyyy HHmm tt"
    $Logfile = "c:\input\Log.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 
    Return "Process count finished on $computername"

# force reboot, after all process requirements met
Restart-Computer -Computer $Computername -Force

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

Open in new window

PowershellVB ScriptSCCMScripting Languages

Avatar of undefined
Last Comment
Ron Shorts

8/22/2022 - Mon
Darrell Porter

$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
         #[Parameters(Mandatory=$false)][switch]$moreThanPing = $false
        )
        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 "Advert Reran on succesfully on $computername"
        }
    } # End Function Start-CCMRerunAdvertisement
    function Send-EmailOffline {
        Send-MailMessage -From “admin@domain.com" -To "admin1@domain.com" -SMTPServer mail.domain.com -Subject “$($computername) is Offline" -Body "$($ComputerName) is Offline, please power on and re-run script."
    }
    function Send-EmailKickit {
        Send-MailMessage -From “admin@domain.com" -To "admin1@domain.com" -SMTPServer mail.domain.com -Subject “$($computername) Completed." -Body "$($ComputerName) 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"
            "TrustedInstaller", "1",            "0"
            "TIWorker",         "1",            "0"
            "OfficeClickToRun", "2",            "0"
'@ | ConvertFrom-Csv | ForEach-Object {$_.Expected = [int]$_.Expected; $_}
        $splat = @{}
        $splat['ComputerName'] = $computerName
        #Write-Output "Monitoring processes on $($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-EmailKickit -ComputerName $computername 
        $Today = (Get-Date).ToString('MM-dd-yyyy'),
        $ExactTime = Get-Date -Format "MM-dd-yyyy HHmm tt"
        $Logfile = "c:\input\Log.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 
        Return "Process count finished on $computername"

        # force reboot, after all process requirements met
        Restart-Computer -Computer $Computername -Force

    } 
    Start-CCMRerunAdvertisement –ComputerName $using:Computer -AdvertisementID "TMC73737" 
    Test-MyConnection -ComputerName $using:Computer
    }
       }
    } # End of initial ForEachRerun workflow
$ComputerList  = Get-Content "c:\input\computerlist.txt"
foreachrerun -Computers $ComputerList

Open in new window

I have tab-corrected (mostly) the above code and added comments where I felt appropriate.

Do you just want the workflow to run 3 times or a subset of options within the workflow?  Do you want the machines to perform the work 3 times, then reboot 3 times and then shutdown?
Ron Shorts

ASKER

@Darrell Porter - thank you.  


Ideally, I would want the machines to perform the work 3 times, with a reboot after each run.


The specific advert forces software and patch updates for QA of new builds. I find on average, this needs to be run 3 times with a reboot after each run for all of the updates to get installed.  When the advert is run, I'm monitoring the processes so I know all updates are completed, then rebooting.


So the workflow would be:


1)  Run the SCCM advert (for patches & updates) on each computer.

2)  Monitor the process count, when count meets expected (updates are complete)

3)  Take action to reboot, send emails, write to logs  (this is where I think I may need to have a sleep command to let the computer fully boot for moving through the workflow too quick).

4)  After reboot, run workflow again, ie; advert, process loop, reboot

5)  Repeat one more time, then shutdown computer


Ideally, an email would only be sent with the final step and shutdown, but not necessary.  


It's not perfect, I know a query to detect if any patches or updates are needed would be more precise, but this is more of a brute force bulk method and then checking the computer in client center for missing patches as a final QA step.


I had a wrapper to run this for 30 minutes with a sleep every 10 minutes, but all this is really doing is stopping the script every 10 minutes so I don't think I'm headed in the right direction.


$timeout = new-timespan -Minutes 30
$sw = [diagnostics.stopwatch]::StartNew()
while ($sw.elapsed -lt $timeout){
  .\Scipt.ps1
  start-sleep -seconds 600
}
write-host "Script cycle complete."

Open in new window


I really appreciate any help!

Darrell Porter

I believe this is what you are looking for - additional comments and code added - loop is located between lines 128 and 131

$OutArray = @()
workflow foreachrerun {
    param([string[]]$computers)
    foreach –parallel ($computer in $computers) {
       InlineScript {
         $n = 3 # Number of iterations through the patch process
         $m = 5 # Number of minutes for the script to pause between iterations of the loop
         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
         #[Parameters(Mandatory=$false)][switch]$moreThanPing = $false
        )
        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 "Advert Reran on succesfully on $computername"
        }
    } # End Function Start-CCMRerunAdvertisement
         function Send-EmailOffline {
            Send-MailMessage -From “admin@domain.com" -To "admin1@domain.com" -SMTPServer mail.domain.com -Subject “$($computername) is Offline" -Body "$($ComputerName) is Offline, please power on and re-run script."
         }
         function Send-EmailKickit {
            Send-MailMessage -From “admin@domain.com" -To "admin1@domain.com" -SMTPServer mail.domain.com -Subject “$($computername) Completed." -Body "$($ComputerName) 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"
                "TrustedInstaller", "1",            "0"
                "TIWorker",         "1",            "0"
                "OfficeClickToRun", "2",            "0"
'@ | ConvertFrom-Csv | ForEach-Object {$_.Expected = [int]$_.Expected; $_}
            $splat = @{}
            $splat['ComputerName'] = $computerName
            #Write-Output "Monitoring processes on $($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-EmailKickit -ComputerName $computername 
            $Today = (Get-Date).ToString('MM-dd-yyyy'),
            $ExactTime = Get-Date -Format "MM-dd-yyyy HHmm tt"
            $Logfile = "c:\input\Log.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 
            Return "Process count finished on $computername"

            # force reboot, after all process requirements met
            Restart-Computer -Computer $Computername -Force

        }
        For ($i=1; $i -le $n; $i++) { # Begin looping $n times - $n is defined at the top of the InlineScript
            Start-CCMRerunAdvertisement –ComputerName $using:Computer -AdvertisementID "TMC73737"
            Start-Sleep -Seconds ($m*60) # Wait $m minutes for computer to reboot - $m is defined at the top of the InlineScript
            Test-MyConnection -ComputerName $using:Computer
        }

       } # End of InlineScript
    } # End of ForEach -parallel
} # End of initial ForEachRerun workflow
$ComputerList  = Get-Content "c:\input\computerlist.txt"
foreachrerun -Computers $ComputerList

Open in new window

This is the best money I have ever spent. I cannot not tell you how many times these folks have saved my bacon. I learn so much from the contributors.
rwheeler23
Ron Shorts

ASKER

Thanks so much for taking the time on this.  So it looks like the iterations and timing worked for both the advert and the process loop - however, none of the reboots occurred.  Any ideas?  


Here is the timeline logged, I had two computers in the list I ran this against and also validated the advert ran on both.


Started @ 12:51pm - process loop began @ 12:56pm...
Got email on computer99 @ 12:58pm, but didn't reboot...
Got email on computer20 @ 1:00pm, but didn't reboot...
Got email on computer99 @ 1:02pm, but didn't reboot...
Got email on computer20 @ 1:05pm, but didn't reboot...
Count finished on computer99 @ 1:09pm, email sent.. still no reboot...
Count finished on computer20 @ 1:10pm, email sent.. still no reboot...


If it helps at all, here are the commands in the advert itself I'm running:

Trigger DataDiscoverRecord (DDR) WMIC.exe /namespace:\root\ccm path sms_client CALL TriggerSchedule "{00000000-0000-0000-0000-000000000003}" /NOINTERACTIVE
Trigger Hardware Inventory WMIC.exe /namespace:\root\ccm path sms_client CALL TriggerSchedule "{00000000-0000-0000-0000-000000000001}" /NOINTERACTIVE
Trigger Software Inventory WMIC.exe /namespace:\root\ccm path sms_client CALL TriggerSchedule "{00000000-0000-0000-0000-000000000113}" /NOINTERACTIVE
Trigger Software Updates WMIC.exe /namespace:\root\ccm path sms_client CALL TriggerSchedule "{00000000-0000-0000-0000-000000000113}" /NOINTERACTIVE
Trigger Software Updates Assignments WMIC.exe /namespace:\root\ccm path sms_client CALL TriggerSchedule "{00000000-0000-0000-0000-000000000108}" /NOINTERACTIVE 
Trigger Request Machine Assignments WMIC.exe /namespace:\root\ccm path sms_client CALL TriggerSchedule "{00000000-0000-0000-0000-000000000021}" /NOINTERACTIVE

Open in new window

Darrell Porter

Yes - I failed to see in what order the functions were called.
$OutArray = @()
workflow foreachrerun {
    param([string[]]$computers)
    foreach –parallel ($computer in $computers) {
       InlineScript {
         $n = 3 # Number of iterations through the patch process
         $m = 5 # Number of minutes for the script to pause between iterations of the loop
         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
         #[Parameters(Mandatory=$false)][switch]$moreThanPing = $false
        )
        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 "Advert Reran on succesfully on $computername"
        }
    } # End Function Start-CCMRerunAdvertisement
         function Send-EmailOffline {
            Send-MailMessage -From “admin@domain.com" -To "admin1@domain.com" -SMTPServer mail.domain.com -Subject “$($computername) is Offline" -Body "$($ComputerName) is Offline, please power on and re-run script."
         }
         function Send-EmailKickit {
            Send-MailMessage -From “admin@domain.com" -To "admin1@domain.com" -SMTPServer mail.domain.com -Subject “$($computername) Completed." -Body "$($ComputerName) Completed and is rebooting."
         }
        function Test-Running {
            param (
            [Parameter(Mandatory=$true)]
            [System.String]$ComputerName
            )
            $processList = @'
                "Name",             "Expected",     "Running"
                "cmd",              "1",            "0"
                "TrustedInstaller", "1",            "0"
                "TIWorker",         "1",            "0"
                "OfficeClickToRun", "2",            "0"
'@ | ConvertFrom-Csv | ForEach-Object {$_.Expected = [int]$_.Expected; $_}
            $splat = @{}
            $splat['ComputerName'] = $computerName
            #Write-Output "Monitoring processes on $($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-EmailKickit -ComputerName $computername 
            $Today = (Get-Date).ToString('MM-dd-yyyy'),
            $ExactTime = Get-Date -Format "MM-dd-yyyy HHmm tt"
            $Logfile = "c:\input\Log.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 
            Return "Process count finished on $computername"

            # force reboot, after all process requirements met
            Restart-Computer -Computer $Computername -Force
        }

        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   
            }
        } # End Test-MyConnection function
           
        For ($i=1; $i -le $n; $i++) { # Begin looping $n times - $n is defined at the top of the InlineScript
            Start-CCMRerunAdvertisement –ComputerName $using:Computer -AdvertisementID "TMC73737"
            Start-Sleep -Seconds ($m*60) # Wait $m minutes for computer to reboot - $m is defined at the top of the InlineScript
            Test-MyConnection -ComputerName $using:Computer
        }
       } # End of InlineScript
    } # End of ForEach -parallel
} # End of initial ForEachRerun workflow
$ComputerList  = Get-Content "c:\input\computerlist.txt"
foreachrerun -Computers $ComputerList

Open in new window

Ron Shorts

ASKER

It still is not issuing the restart command for some reason, is there anything you would suggest to debug?

⚡ FREE TRIAL OFFER
Try out a week of full access for free.
Find out why thousands trust the EE community with their toughest problems.
Darrell Porter

Insert line 112 (before the Restart-Computer command):
Write-host "Now restarting computer $computername"

Open in new window

Insert line 132 (after inserting above line - before the Start-CCMRerunAdvertisement within the loop):
Write-Host "Starting Start-CCMRerunAdvertisement iteration $i"

Open in new window

Insert line line 135 (after inserting above 2 lines - before the Test-Connection within the loop):
Write-Host "Starting Test-Connection iteration $i"

Open in new window

Ron Shorts

ASKER

I had to "Write-Output", had errors with Write-Host with user interaction..


Right after "Process count finished on computer99" it fired an email but no restart...


C:\Scripts\DP2.ps1
Starting Start-CCMRerunAdvertisement iteration 1
Starting Start-CCMRerunAdvertisement iteration 1
Advert Reran on succesfully on computer20
Advert Reran on succesfully on computer99
Starting Test-Connection iteration 1
Starting Test-Connection iteration 1
Ping successful on computer20
Ping successful on computer99
Name             Expected Running
----             -------- -------
cmd                     1       1
TrustedInstaller        1       0
TIWorker                1       0
OfficeClickToRun        2       2
Name             Expected Running
----             -------- -------
cmd                     1       0
TrustedInstaller        1       0
TIWorker                1       0
OfficeClickToRun        2       1
Monitoring processes on computer99
Process count finished on computer99
Starting Start-CCMRerunAdvertisement iteration 2
Monitoring processes on computer20
Name             Expected Running
----             -------- -------
cmd                     1       1
TrustedInstaller        1       0
TIWorker                1       0
OfficeClickToRun        2       2
Advert Reran on succesfully on computer99
Monitoring processes on computer20
Name             Expected Running
----             -------- -------
cmd                     1       1
TrustedInstaller        1       0
TIWorker                1       0
OfficeClickToRun        2       2
Monitoring processes on computer20
Name             Expected Running
----             -------- -------
cmd                     1       1
TrustedInstaller        1       0
TIWorker                1       0
OfficeClickToRun        2       2
Monitoring processes on computer20
Name             Expected Running
----             -------- -------
cmd                     1       1
TrustedInstaller        1       0
TIWorker                1       0
OfficeClickToRun        2       2
Monitoring processes on computer20
Name             Expected Running
----             -------- -------
cmd                     1       1
TrustedInstaller        1       0
TIWorker                1       0
OfficeClickToRun        2       2
Monitoring processes on computer20
Name             Expected Running
----             -------- -------
cmd                     1       1
TrustedInstaller        1       0
TIWorker                1       0
OfficeClickToRun        2       2
Monitoring processes on computer20
Name             Expected Running
----             -------- -------
cmd                     1       1
TrustedInstaller        1       0
TIWorker                1       0
OfficeClickToRun        2       2
Monitoring processes on computer20
Name             Expected Running
----             -------- -------
cmd                     1       1
TrustedInstaller        1       0
TIWorker                1       0
OfficeClickToRun        2       2
Monitoring processes on computer20
Name             Expected Running
----             -------- -------
cmd                     1       1
TrustedInstaller        1       0
TIWorker                1       0
OfficeClickToRun        2       2
Monitoring processes on computer20
Name             Expected Running
----             -------- -------
cmd                     1       1
TrustedInstaller        1       0
TIWorker                1       0
OfficeClickToRun        2       2

Darrell Porter

Let's try the following:
Write-Output "Starting Start-CCMRerunAdvertisement iteration $i for computer $computer"

Open in new window

and
Write-Output "Starting Test-Connection iteration $i for computer $computer"

Open in new window

so we can also see the computer's name.

Also insert the following line before line 79 ($processList = @'):
Write-Output "Computer is $computer"

Open in new window

All of life is about relationships, and EE has made a viirtual community a real community. It lifts everyone's boat
William Peck
Ron Shorts

ASKER

Looks like "Computer is" was blank in the beginning between pings.


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

Open in new window

C:\Scripts\DP2.ps1
Starting Start-CCMRerunAdvertisement iteration 1 for computer
Starting Start-CCMRerunAdvertisement iteration 1 for computer
Advert Reran on succesfully on computer20
Advert Reran on succesfully on computer99
Starting Test-Connection iteration 1 for computer
Starting Test-Connection iteration 1 for computer
Ping successful on computer20
Computer is
Ping successful on computer99
Computer is
Name             Expected Running
----             -------- -------
cmd                     1       0
TrustedInstaller        1       0
TIWorker                1       0
OfficeClickToRun        2       3
Name             Expected Running
----             -------- -------
cmd                     1       0
TrustedInstaller        1       0
TIWorker                1       0
OfficeClickToRun        2       1
Monitoring processes on computer99
Process count finished on computer99
Starting Start-CCMRerunAdvertisement iteration 2 for computer
Monitoring processes on computer20
Name             Expected Running
----             -------- -------
cmd                     1       0
TrustedInstaller        1       0
TIWorker                1       0
OfficeClickToRun        2       3
Advert Reran on succesfully on computer99
Monitoring processes on computer20

Name             Expected Running
----             -------- -------
cmd                     1       0
TrustedInstaller        1       0
TIWorker                1       0
OfficeClickToRun        2       2
Monitoring processes on ws147520
Name             Expected Running
----             -------- -------
cmd                     1       0
TrustedInstaller        1       0
TIWorker                1       0
OfficeClickToRun        2       1
Monitoring processes on computer20
Process count finished on computer20
Starting Start-CCMRerunAdvertisement iteration 2 for computer
Advert Reran on succesfully on computer20
ASKER CERTIFIED SOLUTION
Darrell Porter

THIS SOLUTION ONLY AVAILABLE TO MEMBERS.
View this solution by signing up for a free trial.
Members can start a 7-Day free trial and enjoy unlimited access to the platform.
See Pricing Options
Start Free Trial
GET A PERSONALIZED SOLUTION
Ask your own question & get feedback from real experts
Find out why thousands trust the EE community with their toughest problems.
Ron Shorts

ASKER

So I got this working by using  $using:computer in the Restart command.  


Is there a way to only send and email, along with using a Shutdown command on Iteration 3?


I really appreciate all of your time and expertise.