Public/Invoke-CCMTriggerSchedule.ps1


function Invoke-CCMTriggerSchedule {
    <#
        .SYNOPSIS
            Triggers the specified ScheduleID on local, or remote computers
        .DESCRIPTION
            This script will allow you to invoke the specified ScheduleID on a machine (with optional credentials), providing an optional delay between invokes.
            The function will attempt for a default of 5 minutes to invoke the action, with a 10 second delay inbetween attempts. This is to account for invoke-cimmethod failures.
        .PARAMETER ScheduleID
            Define the schedule IDs to run on the machine, typically found by query another area of WMI
        .PARAMETER Delay
            Specify the delay in seconds between each schedule when more than one is ran - 0-30 seconds
        .PARAMETER Timeout
            Specifies the timeout in minutes after which any individual computer will stop attempting to invoke the schedule IDs. Default is 5 minutes.
        .PARAMETER CimSession
            Provides CimSessions to invoke IDs on
        .PARAMETER ComputerName
            Provides computer names to invoke IDs on
        .EXAMPLE
            C:\PS> Invoke-CCMTriggerSchedule -ScheduleID TST20000
                Performs a TriggerSchedule operation on the TST20000 ScheduleID for the local computer using the default values for Delay and Timeout
        .EXAMPLE
            C:\PS> Invoke-CCMTriggerSchedule -ScheduleID '{00000000-0000-0000-0000-000000000021}'
                Performs a TriggerSchedule operation on the {00000000-0000-0000-0000-000000000021} ScheduleID (Machine Policy Refresh) for the local
                computer using the default values for Delay and Timeout
        .NOTES
            FileName: Invoke-CCMTriggerSchedule.ps1
            Author: Cody Mathis
            Contact: @CodyMathis123
            Created: 2020-01-11
            Updated: 2020-01-11
    #>

    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'ComputerName')]
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string[]]$ScheduleID,
        [parameter(Mandatory = $false)]
        [ValidateRange(0, 30)]
        [ValidateNotNullOrEmpty()]
        [int]$Delay = 0,
        [parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [int]$Timeout = 5,
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'CimSession')]
        [Microsoft.Management.Infrastructure.CimSession[]]$CimSession,
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ComputerName')]
        [Alias('Connection', 'PSComputerName', 'PSConnectionName', 'IPAddress', 'ServerName', 'HostName', 'DNSHostName')]
        [string[]]$ComputerName = $env:ComputerName
    )
    begin {
        $TimeSpan = New-TimeSpan -Minutes $Timeout

        $connectionSplat = @{ }
        $invokeClientActionSplat = @{
            MethodName  = 'TriggerSchedule'
            Namespace   = 'root\ccm'
            ClassName   = 'sms_client'
            ErrorAction = 'Stop'
        }
        $invokeCIMPowerShellSplat = @{
            FunctionsToLoad = 'Invoke-CCMTriggerSchedule'
        }
    }
    process {
        foreach ($ID in $ScheduleID) {
            foreach ($Connection in (Get-Variable -Name $PSCmdlet.ParameterSetName -ValueOnly)) {
                $Computer = switch ($PSCmdlet.ParameterSetName) {
                    'ComputerName' {
                        Write-Output -InputObject $Connection
                        switch ($Connection -eq $env:ComputerName) {
                            $false {
                                if ($ExistingCimSession = Get-CimSession -ComputerName $Connection -ErrorAction Ignore) {
                                    Write-Verbose "Active CimSession found for $Connection - Passing CimSession to CIM cmdlets"
                                    $connectionSplat.Remove('ComputerName')
                                    $connectionSplat['CimSession'] = $ExistingCimSession
                                }
                                else {
                                    Write-Verbose "No active CimSession found for $Connection - falling back to -ComputerName parameter for CIM cmdlets"
                                    $connectionSplat.Remove('CimSession')
                                    $connectionSplat['ComputerName'] = $Connection
                                }
                            }
                            $true {
                                $connectionSplat.Remove('CimSession')
                                $connectionSplat.Remove('ComputerName')
                                Write-Verbose 'Local computer is being queried - skipping computername, and cimsession parameter'
                            }
                        }
                    }
                    'CimSession' {
                        Write-Verbose "Active CimSession found for $Connection - Passing CimSession to CIM cmdlets"
                        Write-Output -InputObject $Connection.ComputerName
                        $connectionSplat.Remove('ComputerName')
                        $connectionSplat['CimSession'] = $Connection
                    }
                }
                $Result = [System.Collections.Specialized.OrderedDictionary]::new()
                $Result['ComputerName'] = $Computer

                if ($PSCmdlet.ShouldProcess("[ComputerName = '$Computer'] [ScheduleID = '$ID']", "Invoke ScheduleID")) {
                    $StopWatch = [System.Diagnostics.Stopwatch]::StartNew()
                    do {
                        try {
                            Remove-Variable MustExit -ErrorAction SilentlyContinue
                            Remove-Variable Invocation -ErrorAction SilentlyContinue
                            $invokeClientActionSplat['Arguments'] = @{
                                sScheduleID = $ID
                            }

                            Write-Verbose "Triggering a [ScheduleID = '$ID'] on $Computer via the 'TriggerSchedule' CIM method"
                            $Invocation = switch ($Computer -eq $env:ComputerName) {
                                $true {
                                    Invoke-CimMethod @invokeClientActionSplat
                                }
                                $false {
                                    $ScriptBlock = [string]::Format('Invoke-CCMTriggerSchedule -ScheduleID {0} -Delay {1} -Timeout {2}', $ID, $Delay, $Timeout)
                                    $invokeCIMPowerShellSplat['ScriptBlock'] = [scriptblock]::Create($ScriptBlock)
                                    Invoke-CIMPowerShell @invokeCIMPowerShellSplat @connectionSplat
                                }
                            }
                        }
                        catch [System.UnauthorizedAccessException] {
                            Write-Error -Message "Access denied to $Computer" -Category AuthenticationError -Exception $_.Exception
                            $MustExit = $true
                        }
                        catch {
                            Write-Warning "Failed to invoke the $ID ScheduleID via CIM. Will retry every 10 seconds until [StopWatch $($StopWatch.Elapsed) -ge $Timeout minutes] Error: $($_.Exception.Message)"
                            Start-Sleep -Seconds 10
                        }
                    }
                    until ($Invocation -or $StopWatch.Elapsed -ge $TimeSpan -or $MustExit)
                    if ($Invocation) {
                        Write-Verbose "Successfully invoked the $ID ScheduleID on $Computer via the 'TriggerSchedule' CIM method"
                        $Result['Invoked'] = $true
                        Start-Sleep -Seconds $Delay
                    }
                    elseif ($StopWatch.Elapsed -ge $TimeSpan) {
                        Write-Error "Failed to invoke the $ID ScheduleID via CIM after $Timeout minutes of retrying."
                        $Result['Invoked'] = $false
                    }
                    $StopWatch.Reset()
                    [pscustomobject]$Result
                }
            }
        }
    }
}