start-at.ps1

function Start-At
{
    <#
    .Synopsis
        Starts scripts at a time or event
    .Description
        Starts scripts at a time, an event, or a change in a table
    .Example
        Start-At -Boot -RepeatEvery "0:30:0" -Name LogTime -ScriptBlock {
            "$(Get-Date)" | Set-Content "$($env:Public)\$(Get-Random).txt"
 
        }
    .Link
        Use-Schematic
    #>

    [CmdletBinding(DefaultParameterSetName='StartAtTime')]
    [OutputType([Nullable])]
    param(
    # The scriptblock that will be run
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [ScriptBlock[]]$ScriptBlock,    
    
    # The time the script will start
    [Parameter(Mandatory=$true, ParameterSetName='StartAtTime')]
    [DateTime]$Time,

    # The event ID of interest
    [Parameter(Mandatory=$true, ParameterSetName='StartAtSystemEvent')]
    [Uint32]$EventId,

    # The event log where the eventID is found
    [Parameter(Mandatory=$true, ParameterSetName='StartAtSystemEvent')]
    [string]$EventLog,

    # The table name that contains data to process
    [Parameter(Mandatory=$true, ParameterSetName='StartAtTableData')]
    [Parameter(Mandatory=$true, ParameterSetName='StartAtSqlData')]
    [string]$TableName,

    # The name of the user table. If an OwnerID is found on an object, and user is found in the a usertable, then the task will be run as that user
    [Parameter(ParameterSetName='StartAtTableData')]
    [Parameter(ParameterSetName='StartAtSqlData')]
    [string]$UserTableName,

    # The filter used to scope queries for table data
    [Parameter(Mandatory=$true, ParameterSetName='StartAtTableData')]
    [string]$Filter,
    
    # The filter used to scope queries for table data
    [Parameter(Mandatory=$true, ParameterSetName='StartAtSQLData')]
    [string]$Where,

    # The name of the setting containing the storage account
    [Parameter(ParameterSetName='StartAtTableData')]    
    [Parameter(ParameterSetName='StartAtSqlData')]
    [Parameter(ParameterSetName='StartAtNewEmail')]
    [string]$StorageAccountSetting = "AzureStorageAccountName",

    # The name of the setting containing the storage key
    [Parameter(ParameterSetName='StartAtTableData')]    
    [Parameter(ParameterSetName='StartAtSqlData')]
    [Parameter(ParameterSetName='StartAtNewEmail')]
    [string]$StorageKeySetting = "AzureStorageAccountKey",

    # Clears a property on the object when the item has been handled
    [Parameter(ParameterSetName='StartAtTableData')]    
    [string]$ClearProperty,

    # The name of the setting containing the email username
    [Parameter(ParameterSetName='StartAtNewEmail',Mandatory=$true)]
    [string]$EmailUserNameSetting,

    # The name of the setting containing the email password
    [Parameter(ParameterSetName='StartAtNewEmail',Mandatory=$true)]
    [string]$EmailPasswordSetting,

    # The display name of the inbox receiving the mail.
    [Parameter(ParameterSetName='StartAtNewEmail',Mandatory=$true)]
    [string]$SentTo,

    # The name of the setting containing the storage key
    [Parameter(ParameterSetName='StartAtSQLData')]    
    [string]$ConnectionStringSetting = "SqlAzureConnectionString",

    # The partition containing user information. If the items in the table have an owner, then the will be run in an isolated account.
    [Parameter(ParameterSetName='StartAtTableData')]    
    [string]$UserPartition = "Users",

    # The timespan in between queries
    [Parameter(ParameterSetName='StartAtTableData')]
    [Parameter(ParameterSetName='StartAtSQLData')]
    [Parameter(ParameterSetName='StartAtNewEmail')]
    [Timespan]$CheckEvery = "0:15:0",

    # The timespan in between queries
    [Parameter(ParameterSetName='StartAtTableData')]
    [Parameter(ParameterSetName='StartAtSQLData')]
    [Parameter(ParameterSetName='StartAtNewEmail')]
    [Switch]$SortDescending,
    
    # The randomized delay surrounding a task start time. This can be used to load-balance expensive executions
    [Parameter(ParameterSetName='StartAtTime')]
    [Timespan]$Jitter,

    # If set, the task will be started every day at this time
    [Parameter(ParameterSetName='StartAtTime')]
    [Switch]$EveryDay,
    
    # The interval (in days) in between running the task
    [Parameter(ParameterSetName='StartAtTime')]
    [Switch]$DayInterval,        


    # If set, the task will be started whenever the machine is locked
    [Parameter(ParameterSetName='StartAtLock')]
    [Switch]$Lock,


    # If set, the task will be started whenever the machine is unlocked
    [Parameter(Mandatory=$true,ParameterSetName='StartAtBoot')]
    [Switch]$Boot,

    # If set, the task will be started whenever the machine is unlocked
    [Parameter(Mandatory=$true,ParameterSetName='StartAtAnyLogon')]
    [Switch]$Logon,
    
    # If set, the task will be started whenever the machine is unlocked
    [Parameter(Mandatory=$true,ParameterSetName='StartAtUnlock')]
    [Switch]$Unlock,

    # If set, the task will be started whenever a user logs onto the local machine
    [Parameter(Mandatory=$true,ParameterSetName='StartAtLocalLogon')]
    [Switch]$LocalLogon,

    # If set, the task will be started whenever a user logs off of a local machine
    [Parameter(Mandatory=$true,ParameterSetName='StartAtLocalLogoff')]
    [Switch]$LocalLogoff,

    # If set, the task will be started whenever a user disconnects via remote deskop
    [Parameter(Mandatory=$true,ParameterSetName='StartAtRemoteLogon')]
    [Switch]$RemoteLogon,

    # If set, the task will be started whenever a user disconnects from remote desktop
    [Parameter(Mandatory=$true,ParameterSetName='StartAtRemoteLogoff')]
    [Switch]$RemoteLogoff,

    # Starts the task as soon as possible
    [Parameter(Mandatory=$true,ParameterSetName='StartASAP')]
    [Alias('ASAP')]
    [Switch]$Now,

    # IF provided, will scope logons or connections to a specific user
    [Parameter(ParameterSetName='StartAtLock')]
    [Parameter(ParameterSetName='StartAtUnLock')]
    [Parameter(ParameterSetName='StartAtAnyLogon')]
    [Parameter(ParameterSetName='StartAtAnyLogoff')]
    [Parameter(ParameterSetName='StartAtLocalLogon')]
    [Parameter(ParameterSetName='StartAtLocalLogoff')]
    [Parameter(ParameterSetName='StartAtRemoteLogon')]
    [Parameter(ParameterSetName='StartAtRemoteLogoff')]
    [string]$ByUser,



    # The user running the script
    [Management.Automation.PSCredential]
    $As,


    # The name of the computer the task will be run on. If not provided, the task will be run locally
    [Alias('On')]
    [string]
    $ComputerName,

    # If set, the task will repeat at this frequency.
    [Timespan]$RepeatEvery,

    # If set, the task will repeat for up to this timespan. If not set, the task will repeat indefinately.
    [Timespan]$RepeatFor,

    # A name for the task.
    [string]
    $Name,

    # The name of the folder within Task Scheduler.
    [string]
    $Folder,

    # If set, will not exist the started task.
    [Switch]
    $NoExit,

    # The priority of the scheduled task
    [Uint32]
    $TaskPriority = 4,

    # How multiple instances of a task should be treated. By default, multiple instances are queued.
    [ValidateSet("StopExisting", "Queue", "Parallel", "IgnoreNew")]
    [string]
    $MultipleInstancePolicy = "Queue",

    # If set, the task will self destruct after it as run once.
    [Switch]
    $SelfDestruct,

    # If set, tasks registered with a credential will be registered with TASK_LOGON_PASSWORD, which will prevent the scheduled task from popping up a visible window.
    [Switch]
    $NotInteractive
    )

    process {
        #region Connect to the scheduler
        $sched = New-Object -ComObject Schedule.Service
        $sched.Connect()
        $task = $sched.NewTask(0)
        $task.Settings.Priority = $TaskPriority
        #endregion Connect to the scheduler


        $description = ""
        #region Add the actions to the task
        foreach ($sb in $ScriptBlock) {

            $action = $task.Actions.Create(0)
            $action.Path = Join-Path $psHome "PowerShell.exe" 
        
        
            $action.Arguments = " -WindowStyle Minimized -Sta"
        
        
            if ($NoExit) {
                $Action.Arguments += " -NoExit"
            }
            $encodedCommand = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($sb))
            $action.Arguments+= " -encodedCommand $encodedCommand"

        }
        #endregion Add the actions to the task

        if ($PSCmdlet.ParameterSetName -eq 'StartAtTime') {
        
                $days = (Get-Culture).DateTimeFormat.DayNames
                $months  = (Get-Culture).DateTimeFormat.MonthNames
                if ($PSBoundParameters.EveryDay -or $PSBoundParameters.DayInterval) {
                    $dailyTrigger = $task.Triggers.Create(2)

                    if ($psBoundParameters.DayInterval) {
                        $dailyTrigger.DaysInterval = $psBoundParameters.DayInterval
                    }
                } else {
                    # One time
                    $timeTrigger = $task.Triggers.Create(1)

                    
                }


        } elseif ($psCmdlet.ParameterSetName -eq 'StartAtLogon') {
            $logonTrigger= $task.Triggers.Create(9)
            $description += " At Logon "
                
            
        } elseif ($psCmdlet.ParameterSetName -eq 'StartAtSystemEvent') {
            $evtTrigger= $task.Triggers.Create(0)
            $evtTrigger.Subscription = "
<QueryList>
    <Query Id='0' Path='$($EventLog)'>
        <Select Path='$($EventLog)'>
            *[System[EventID=$($EventId)]]
        </Select>
    </Query>
</QueryList>
"
                
            
            $description += " At Event $EventId"
                
            
        } elseif ($psCmdlet.ParameterSetName -eq 'StartAtLocalLogon') {
            $stateTrigger= $task.Triggers.Create(11)
            $stateTrigger.StateChange = 1 
            $description += " At Local Logon "
                
            
        } elseif ($psCmdlet.ParameterSetName -eq 'StartAtLocalLogoff') {
            $stateTrigger= $task.Triggers.Create(11)
            $stateTrigger.StateChange = 2 
            $description += " At Local Logoff "
                
            
        } elseif ($psCmdlet.ParameterSetName -eq 'StartAtRemoteLogoff') {
            $stateTrigger= $task.Triggers.Create(11)
            $stateTrigger.StateChange = 3 
            $description += " At Remote Logon "
                
            
        } elseif ($psCmdlet.ParameterSetName -eq 'StartAtRemoteLogoff') {
            $stateTrigger= $task.Triggers.Create(11)
            $stateTrigger.StateChange = 4 
            $description += " At Remote Logoff "
                
            
        } elseif ($psCmdlet.ParameterSetName -eq 'StartAtLock') {
            $stateTrigger= $task.Triggers.Create(11)
            $stateTrigger.StateChange = 7 
            $description += " At Lock"
                
            
        } elseif ($psCmdlet.ParameterSetName -eq 'StartAtUnlock') {
            $stateTrigger= $task.Triggers.Create(11)
            $stateTrigger.StateChange = 8 
            $description += " At Unlock "
                
            
        } elseif ($psCmdlet.ParameterSetName -eq 'StartASAP') {
            $regTrigger = $task.Triggers.Create(7)
            $description += " ASAP "
        } elseif ($psCmdlet.ParameterSetName -eq 'StartAtBoot') {
            $bootTrigger = $task.Triggers.Create(8)
            
            $description += " OnBoot "
        } elseif ('StartAtTableData', 'StartAtSqlData', 'StartAtNewEmail' -contains $PSCmdlet.ParameterSetName) {            
            if (-not $PSBoundParameters.As) {
                Write-Error "Must supply credential for table based tasks"
                return 
            }
            # Schedule it as the user that will check

            $description += " New SQL Data from $TableName "
            IF ($PSCmdlet.ParameterSetName -eq 'StartAtNewEmail') {
                $check= "
Get-Email -UserNameSetting '$EmailUserNameSetting' -PasswordSetting '$EmailPasswordSetting' -Unread -Download -To '$SentTo'"

            } elseif ($psCmdlet.ParameterSetName -eq 'StartAtSqlData') {
                $check= "
Select-Sql -ConnectionStringOrSetting '$ConnectionStringSetting' -FromTable '$tableName' -Where '$Where'"

            } elseif ($psCmdlet.ParameterSetName -eq 'StartAtTableData') {
                $check = "
Search-AzureTable -TableName '$TableName' -Filter `"$Filter`" -StorageAccount `$storageAccount -StorageKey `$storageKey"

                
                if ((-not $UserTableName) -and $TableName) {
                    $UserTableName = $TableName
                }

            } 
            
                


            $saveMyCred = "
Add-SecureSetting -Name '$StorageAccountSetting' -String '$(Get-SecureSetting $StorageAccountSetting -ValueOnly)'
Add-SecureSetting -Name '$StorageKeySetting' -String '$(Get-SecureSetting $StorageKeySetting -ValueOnly)'
                "


            $saveMyCred = [ScriptBlock]::Create($saveMyCred)
            # Start-At -ScriptBlock $saveMyCred -As $As -Now
            
            

            

            $checkTable = "
Import-Module Pipeworks
`$storageAccount = Get-SecureSetting '$StorageAccountSetting' -ValueOnly
`$storageKey = Get-SecureSetting '$StorageKeySetting' -ValueOnly
`$verbosePreference='$VerbosePreference'
Write-Verbose 'Getting Data'
$check |
    Sort-Object {
        if (`$_.Timestamp) {
            (`$_.Timestamp -as [Datetime])
        } elseif (`$_.DateTimeSent -as [DateTime]) {
            `$_.DateTimeSent -as [DateTime]
        } else {
            `"`"
        }
    } -Descending:`$$SortDescending |
    Foreach-Object -Begin {
         
    }-Process {
        `$item = `$_
         
        `$userTableName = '$UserTableName'
         
        if (-not `$userTableName) {
            `$error.Clear()
             
            Write-Verbose 'Script Started'
            `$scriptOutput = . {
                $ScriptBlock
            }
            Write-Verbose 'Script Complete'
            `$errorList = @(`$error)
            `$updatedItem =`$item |
                Add-Member NoteProperty ScriptResults `$scriptOutput -Force -PassThru
 
 
            if (`$errorList) {
                `$updatedItem = `$updatedItem |
                    Add-Member NoteProperty ScriptErrors `$errorList -Force -PassThru
            }
 
        } else {
            if (`$item.From.Address) {
                `$userFound = Search-AzureTable -TableName '$UserTableName' -Filter `"PartitionKey eq '$UserPartition' and UserEmail eq '`$(`$item.From.Address)'`" -StorageAccount `$storageAccount -StorageKey `$storageKey
            } elseif (`$item.OwnerID) {
                # Run it as the owner
                `$userFound = Search-AzureTable -TableName '$UserTableName' -Filter `"PartitionKey eq '$UserPartition' and RowKey eq '`$(`$item.OwnerID)'`" -StorageAccount `$storageAccount -StorageKey `$storageKey
            }
            if (-not `$userFound) {
                Write-Error 'User Not Found'
                return
            }
             
            if (-not `$item.OwnerID) {
                return
            }
 
 
            `$id = `$item.OwnerID[0..7+-1..-12] -join ''
            `$userExistsOnSystem = net user `"`$id`" 2>&1
             
            if (`$userExistsOnSystem[0] -as [Management.Automation.ErrorRecord]) {
                # They don't exist, make them
                # `$completed = net user `"`$id`" `"`$(`$userFound.PrimaryAPIKey)`" /add /y
                Write-Verbose 'Creating User'
                `$objOu = [ADSI]`"WinNT://`$env:ComputerName`"
                `$objUser = `$objOU.Create(`"User`", `$id)
                `$objUser.setpassword(`$userFound.PrimaryAPIKey)
                `$objUser.SetInfo()
                `$objUser.description = `$userFound.UserID
                `$objUser.SetInfo()
            }
 
            `$targetPath = Split-path `$home
            `$targetPath = Join-Path `$targetPath `$id
 
            `$innerScript = {
                `$in = `$args
                     
                foreach (`$item in `$in) {
                    if (`$item -is [Array]) {
                        `$item = `$item[0]
                    }
                    . {
                        $ScriptBlock
                    } 2>&1
                     
                    `$null = `$item
                }
            }
 
            `$asCred = New-Object Management.Automation.PSCredential `"`$id`", (ConvertTo-SecureString -Force -AsPlainText `"`$(`$userFound.PrimaryAPIKey)`")
            `$scriptOutput = Start-Job -ScriptBlock `$innerScript -Credential `$asCred -ArgumentList `$item |
                Wait-Job |
                Receive-Job
 
            `$jobWorked = `$?
 
            `$compressedResults = (`$scriptOutput | Write-PowerShellHashtable) | Compress-Data -String { `$_ }
 
            `$updatedItem =`$item |
                Add-Member NoteProperty ScriptResults (`$compressedResults) -Force -PassThru
 
 
            if (`$jobWorked -and `$item.RowKey -and `$item.PartitionKey) {
                `$clearProperty = '$ClearProperty'
                if (`$clearProperty) {
                    `$null = `$updatedItem.psobject.properties.Remove(`$clearProperty)
                }
                `$updatedItem |
                    Update-AzureTable -TableName '$TableName' -Value { `$_ }
            }
             
        }
         
         
          
    }
"


            $checkTable = [ScriptBlock]::Create($checkTable)

            Start-At -Boot -As $as -ScriptBlock $checkTable -RepeatEvery $CheckEvery -NoExit:$NoExit -Name:"${Name}_AtBoot" -Folder:$Folder
            Start-At -Now -As $as -ScriptBlock $checkTable -RepeatEvery $CheckEvery -NoExit:$NoExit -Name:"${Name}_Now" -Folder:$Folder
        }


        
        

        if ($task.Triggers.Count) {
            $task.Settings.MultipleInstances = if ($MultipleInstancePolicy -eq 'StopExisting') {
                 3
            } elseif ($MultipleInstancePolicy -eq 'Queue') {
                1
            } elseif ($MultipleInstancePolicy -eq 'Parallel') {
                0
            } elseif ($MultipleInstancePolicy -eq 'IgnoreNew') {
                2
            }
            foreach ($trig in $task.Triggers) {
                if ($PSBoundParameters.Time) {
                    $trig.StartBoundary = $Time.ToString("s")
                } else {
                    $trig.StartBoundary = [DateTime]::Now.ToString("s")
                }
                if ($PSBoundParameters.RepeatEvery)  {
                    $trig.Repetition.Interval = "PT$($RepeatEvery.TotalMinutes -as [uint32])M"
                }
                if ($PSBoundParameters.RepeatFor) {
                    $trig.Repetition.Duration = "PT$($RepeatFor.TotalMinutes -as [uint32])M"
                }
                if ($PSBoundParameters.Jitter) {
                    $trig.RandomDelay = "PT$($Jitter.TotalMinutes -as [uint32])M"
                }
                if ($psBoundParameters.ByUser) {
                    $trig.UserID = $PSBoundParameters.ByUser
                    $description += " ByUser $($psBoundParameters.ByUser.Replace('\','_'))"
                }

            }

            $taskNAme = if ($Name) {
                $Name
            } else {
                if ($as) {
                    "Start-At $Description as $($As.GetNetworkCredential().UserName) "
                } else {
                    "Start-At $Description"
                }
            }


            
            

            $taskPath = 
                if ($Folder) {
                    Join-Path $folder $taskNAme 
                } else {
                    $taskNAme 
                }

            if ($selfDestruct) {
                $removeAction = $task.Actions.Create(0)
                $removeAction.Path = "schtasks"                                
                $removeAction.Arguments = "/delete /tn `"$TaskPath`" /f"
        
            }

            if ($as) {
                $task.Principal.RunLevel = 1                
                if ($NotInteractive) {
                    $registeredTask = $sched.GetFolder("").RegisterTask($taskPath, $task.XmlText, 6, $As.UserName, $As.GetNetworkCredential().Password, 1, $null)
                }  else {
                    $registeredTask = $sched.GetFolder("").RegisterTask($taskPath, $task.XmlText, 6, $As.UserName, $As.GetNetworkCredential().Password, 6, $null)
                }
                
            } else {
                $registeredTask = $sched.GetFolder("").RegisterTask($taskPath, $task.XmlText, 6, "", "", 3, $null)
            }
        }
        




        
    }
}