Functions/GenXdev.Windows/Initialize-ScheduledTaskScripts.ps1

################################################################################
<#
.SYNOPSIS
Creates scheduled tasks that run PowerShell scripts at specified intervals.
 
.DESCRIPTION
Creates and configures scheduled tasks that execute PowerShell scripts at various
intervals including:
- System startup
- User logon
- Every hour of specific days (e.g., Monday at 13:00)
- Daily at specific hours (e.g., every day at 15:00)
Each task runs with elevated privileges under the current user's context.
 
.PARAMETER FilePath
The directory path where the PowerShell scripts for each task will be created.
If not specified, scripts are created in a 'ScheduledTasks' folder in the parent
directory.
 
.PARAMETER Prefix
A prefix string added to all task names for grouping and identification.
Default is "PS".
 
.EXAMPLE
Initialize-ScheduledTaskScripts -FilePath "C:\Tasks" -Prefix "MyTasks"
 
.EXAMPLE
Initialize-ScheduledTaskScripts
#>

function Initialize-ScheduledTaskScripts {

    [CmdletBinding()]
    param(
        ###########################################################################
        [parameter(
            Position = 0,
            Mandatory = $false,
            HelpMessage = "The directory path where task scripts will be created"
        )]
        [string] $FilePath = "",
        ###########################################################################
        [parameter(
            Position = 1,
            Mandatory = $false,
            HelpMessage = "Prefix for the scheduled task names"
        )]
        [string] $Prefix = "PS"
        ###########################################################################
    )

    begin {

        # array of weekdays for creating weekly scheduled tasks
        $daysOfWeek = @(
            "Sunday", "Monday", "Tuesday", "Wednesday",
            "Thursday", "Friday", "Saturday"
        )

        # get current UTC time for calculating task start times
        $now = [DateTime]::UtcNow

        # get current user credentials for task execution context
        $credential = Get-Credential -UserName `
        ([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)

        # set default path if none provided and ensure directory exists
        if ([string]::IsNullOrWhiteSpace($FilePath)) {
            $FilePath = Expand-Path `
                -FilePath "$PSScriptRoot\..\..\..\..\..\ScheduledTasks\" `
                -CreateDirectory
        }
        else {
            $FilePath = Expand-Path -FilePath $FilePath
        }

        # set global workspace for task execution context
        $Global:WorkspaceFolder = Expand-Path "$PSScriptRoot\..\..\..\..\..\"

        Write-Verbose "Task scripts will be created in: $FilePath"
        Write-Verbose "Tasks will be prefixed with: $Prefix"
    }

    process {

        ###########################################################################
        <#
        .SYNOPSIS
        Creates a new scheduled task with specified parameters.
 
        .DESCRIPTION
        Helper function that creates a PowerShell script file and corresponding
        scheduled task with specified trigger conditions.
 
        .PARAMETER TaskName
        Name of the scheduled task to create.
 
        .PARAMETER Description
        Description of what the task does.
 
        .PARAMETER Trigger
        Scheduled task trigger object defining when the task runs.
        #>

        function New-TaskDefinition {
            param([string]$TaskName, [string]$Description, $Trigger)

            # create full path for the task's PowerShell script
            $scriptPath = Expand-Path -CreateDirectory `
                -FilePath "$FilePath\$TaskName.ps1"

            # create script file with logging if it doesn't exist
            if (-not (Test-Path $scriptPath -ErrorAction SilentlyContinue)) {
                $scriptContent = @"
# $Description
 
$($Description | ConvertTo-Json) | Out-File '$Global:WorkspaceFolder\scheduledtasks.log.txt' -Append
 
"@

                $null = Set-Content -Path $scriptPath -Value $scriptContent
            }

            # create task only if it doesn't already exist
            if (-not (Get-ScheduledTask -TaskName $TaskName `
                        -TaskPath "\$Prefix\" -ErrorAction SilentlyContinue)) {

                Write-Verbose "Creating scheduled task: \$Prefix\$TaskName"
                Write-Verbose "Task description: $Description"

                # configure the PowerShell execution command
                $actionArguments = "-ExecutionPolicy Bypass -NoLogo -Command & `
                    `"'$scriptPath'`""

                $action = New-ScheduledTaskAction `
                    -Execute ((Get-Command "pwsh.exe").source) `
                    -Argument $actionArguments `
                    -Id "Exec $TaskName".Replace(" ", "_") `
                    -WorkingDirectory $Global:WorkspaceFolder

                # configure task execution settings
                $settings = New-ScheduledTaskSettingsSet `
                    -AllowStartIfOnBatteries `
                    -DontStopIfGoingOnBatteries `
                    -Hidden `
                    -StartWhenAvailable
                $settings.AllowHardTerminate = $true
                $settings.ExecutionTimeLimit = 'PT1H'
                $settings.Volatile = $false

                # set task expiration 99 years in future
                $trigger.EndBoundary = $now.AddYears(99).ToString(
                    "yyyy-MM-dd'T'HH:mm:ss")

                # convert credential password for task registration
                $ptr = [Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode(
                    $credential.Password)
                $plainPassword = [Runtime.InteropServices.Marshal]::PtrToStringUni($ptr)
                [Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocUnicode($ptr)

                # register the task with configured parameters
                $taskParams = @{
                    TaskName    = $TaskName
                    User        = $credential.UserName
                    Password    = $plainPassword
                    RunLevel    = "Highest"
                    Action      = $action
                    Description = $Description
                    Settings    = $settings
                    Trigger     = $trigger
                    TaskPath    = $Prefix
                    Force       = $true
                }
                Register-ScheduledTask @taskParams
            }
        }

        # create system startup triggered task
        New-TaskDefinition `
            -TaskName "${Prefix}_at_startup" `
            -Description "Scheduled-task executed at startup" `
            -Trigger (New-ScheduledTaskTrigger -AtStartup)

        # create user logon triggered task
        New-TaskDefinition `
            -TaskName "${Prefix}_at_logon" `
            -Description "Scheduled-task executed at logon" `
            -Trigger (New-ScheduledTaskTrigger -AtLogOn)

        # create weekly tasks for each day and hour combination
        foreach ($day in $daysOfWeek) {
            for ($hour = 0; $hour -lt 24; $hour++) {
                $taskName = "$Prefix_$($day.ToLower())_$($hour.ToString('D2'))00h_utc"
                $description = "Scheduled-task for $day at $($hour.ToString('D2')):00h"

                # calculate next occurrence of this day and hour
                $dayDiff = ([int]$now.DayOfWeek) - $daysOfWeek.IndexOf($day)
                $at = $now.Date.AddDays($dayDiff).AddHours($hour)
                if ($at -lt $now) { $at = $at.AddDays(7) }

                New-TaskDefinition `
                    -TaskName $taskName `
                    -Description $description `
                    -Trigger (New-ScheduledTaskTrigger -Weekly -DaysOfWeek $day -At $at)
            }
        }

        # create daily tasks for each hour
        for ($hour = 0; $hour -lt 24; $hour++) {
            $taskName = "$Prefix_daily_$($hour.ToString('D2'))00h_utc"
            $description = "Scheduled-task executed Daily at $($hour.ToString('D2')):00h"

            # calculate next occurrence of this hour
            $at = $now.Date.AddHours($hour)
            if ($at -lt $now) { $at = $at.AddDays(1) }

            New-TaskDefinition `
                -TaskName $taskName `
                -Description $description `
                -Trigger (New-ScheduledTaskTrigger -Daily -At $at)
        }
    }

    end {
    }
}
################################################################################