DSCResources/Grani_ScheduleTask/Grani_ScheduleTask.psm1

#region Initialize

function Initialize {
    # Enum for Ensure
    Add-Type -TypeDefinition @"
    namespace Grani_ScheduleTask
    {
        public enum EnsureType
        {
            Present,
            Absent
        }
    }
"@
 -ErrorAction SilentlyContinue

    #-- PublicEnum Enum for ScheduledTaskTest --#
    Add-Type -TypeDefinition @"
    namespace Grani_ScheduleTask
    {
        public enum ScheduledParameterType
        {
            Root,
            Actions,
            Principal,
            Settings,
            Triggers
        }
    }
"@
 -ErrorAction SilentlyContinue

    #-- PublicEnum Enum for ScheduledTaskTest Property --#
    Add-Type -TypeDefinition @"
    namespace Grani_ScheduleTask
    {
        public enum ScheduledTaskPropertyType
        {
            TaskName,
            Description,
            Execute,
            Argument,
            WorkingDirectory,
            Credential,
            RunLevel,
            Compatibility,
            ExecutionTimeLimit,
            Hidden,
            Disable,
            ScheduledAt,
            RepetitionIntervalTimeSpanString,
            RepetitionDurationTimeSpanString,
            Daily,
            Once,
            AtSartup,
            AtLogOn,
            AtLogOnUserId
        }
    }
"@
 -ErrorAction SilentlyContinue
}

Initialize

#endregion

#region Message Definition

$verboseMessages = Data {
    ConvertFrom-StringData -StringData @"
        EnsureDetectAbsent = Ensure detected as Absent. Removing existing ScheduledTask for TaskPath '{0}', TaskName '{1}'.
        EnsureDetectPresent = Ensure detected as Present. Setting ScheduledTask for TaskPath '{0}', TaskName '{1}'.
        DisableDetected = Disabled detected as $true. Disabling task and exit configuration.
"@

}

$debugMessages = Data {
    ConvertFrom-StringData -StringData @"
        CheckSchedulerAtLogOn = Checking Trigger is : AtLogOn
        CheckSchedulerAtStartup = Checking Trigger is : AtStartup
        CheckSchedulerDaily = Checking Trigger is : Daily
        CheckSchedulerOnce = Checking Trigger is : Once
        CheckScheduleTaskExist = Checking {0} is exists with : {1}
        CheckScheduleTaskParameter = Checking {0} is match with : {1}
        CheckScheduleTaskParameterTimeSpan = Checking {0} is match with : {1}min
        CheckSchedulerUserId = Checking UserId is exists with : {0}
        CreateTask = Creating Task Scheduler Name '{0}', Path '{1}'
        ScheduleTaskResult = {0} : {1} ({2})
        ScheduleTaskTimeSpanResult = {0} : {1} ({2}min)
        SetAction = Setting Action Execute : '{0}', Argument : '{1}', WorkingDirectory : '{2}'.
        SetDescription = Setting Description : '{0}'.
        SetDisable = Setting ScheduledTask Disable : '{0}'.
        SetTrigger = Setting Trigger RepetitionIntervalTimeSpanString : '{0}', RepetitionDurationTimeSpanString : '{1}', ScheduledAt : '{2}', Daily : '{3}', Once : '{4}'.
        SkipNoneUseParameter = Skipping {0} as value not passed to function.
        SkipNullPassedParameter = Skipping {0} as passed value is null.
        SkipPrincipal = Skip Credential. Using System for Principal.
        UsePrincipal = Using principal with Credential. Execution will be fail if not elevated.
        ValidateTaskPathChar = Validated Task Path for {0} char expected to be '\\' but was not. Add '\\'. TaskPath : {1}, NewTaskPath : {2}
"@

}

$errorMessages = Data {
    ConvertFrom-StringData -StringData @"
        InvalidTrigger = Invalid Operation detected, you can't set same or greater timespan for RepetitionInterval '{0}' than RepetitionDuration '{1}'.
        ExecuteBrank = Invalid Operation detected, Execute detected as blank. You must set executable string.
        ScheduleAtArgumentLength = Argument length not match with current ScheduledAt {0} and passed ScheduledAt {1}.
        ScheduleRepetitionArgumentLength = Argument length not match with current Scheduled Repetition {0} and passed ScheduledAt {1}.
        ScheduleAtNullException = ScheduledAt detected as null. You must set at least 1 ScheduledAt to set ScheduledTask as Present.
        ScheduleTriggerNullException = None of Schedule trigger detected. Please set one of trigger from : RepetitionIntervalTimeSpanString, RepetitionDurationTimeSpanString, Daily, Once, AtSartup, AtLogOn
"@

}

#endregion

#region *-TargetResource

function Get-TargetResource {
    [OutputType([HashTable])]
    [CmdletBinding(DefaultParameterSetName = "Repetition")]
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateSet("Present", "Absent")]
        [System.String]$Ensure,

        [parameter(Mandatory = $true)]
        [System.String]$TaskName,

        [parameter(Mandatory = $false)]
        [System.String]$TaskPath = "\",

        [parameter(Mandatory = $false)]
        [System.String]$Description,

        [parameter(Mandatory = $false)]
        [System.String]$Execute,

        [parameter(Mandatory = $false)]
        [System.String]$Argument,

        [parameter(Mandatory = $false)]
        [System.String]$WorkingDirectory,

        [parameter(Mandatory = $false)]
        [System.Management.Automation.PSCredential]$Credential = [PSCredential]::Empty,

        [parameter(Mandatory = $false)]
        [ValidateSet("Highest", "Limited")]
        [System.String]$Runlevel,

        [parameter(Mandatory = $false)]
        [ValidateSet("At", "Win8", "Win7", "Vista", "V1")]
        [System.String]$Compatibility,

        [parameter(Mandatory = $false)]
        [System.Int64]$ExecuteTimeLimitTicks = [TimeSpan]::FromDays(3).Ticks,

        [parameter(Mandatory = $false)]
        [System.Boolean]$Hidden,

        [parameter(Mandatory = $false)]
        [System.Boolean]$Disable = $true,

        [parameter(Mandatory = $false)]
        [System.DateTime[]]$ScheduledAt,

        [parameter(Mandatory = $false, parameterSetName = "Repetition")]
        [string[]]$RepetitionIntervalTimeSpanString = @([TimeSpan]::FromHours(1).ToString()),

        [parameter(Mandatory = $false, parameterSetName = "Repetition")]
        [string[]]$RepetitionDurationTimeSpanString = @([TimeSpan]::MaxValue.ToString()),

        [parameter(Mandatory = $false, parameterSetName = "Daily")]
        [System.Boolean]$Daily,

        [parameter(Mandatory = $false, parameterSetName = "Once")]
        [System.Boolean]$Once,

        [parameter(Mandatory = $false, parameterSetName = "AtStartup")]
        [System.Boolean]$AtStartup,

        [parameter(Mandatory = $false, parameterSetName = "AtLogOn")]
        [System.Boolean]$AtLogOn,

        [parameter(Mandatory = $false, parameterSetName = "AtLogOn")]
        [System.String]$AtLogOnUserId = ""
    )

    $param = @{}

    # Task Path validation
    $validatedTaskPath = ValidateTaskPathLastChar -taskPath $taskPath
    $param.TaskPath = ValidateTaskPathFirstChar -taskPath $validatedTaskPath

    if ($Disable) {
        Write-Debug "Disable"
        @(
            'TaskName',
            'Disable'
        ) `
            | where {$PSBoundParameters.ContainsKey($_)} `
            | % { $param.$_ = Get-Variable -Name $_ -ValueOnly }
    }
    else {
        # Credential param
        if (($PSBoundParameters.ContainsKey("Credential")) -or ([PSCredential]::Empty -ne $Credential)) {
            $param.Credential = $Credential
        }

        # Trigger param
        if ($PSBoundParameters.ContainsKey("Once")) {
            $param.Once = $Once
        }
        elseif ($PSBoundParameters.ContainsKey("Daily")) {
            $param.Daily = $Daily
        }
        elseif ($PSBoundParameters.ContainsKey("AtStartup")) {
            $param.AtStartup = $AtStartup
        }
        elseif ($PSBoundParameters.ContainsKey("AtLogOn")) {
            $param.AtLogOn = $AtLogOn
            if ($PSBoundParameters.ContainsKey("AtLogOnUserId")) {
                $param.AtLogOnUserId = $AtLogOnUserId
            }
        }
        else {
            if ($PSBoundParameters.ContainsKey('RepetitionIntervalTimeSpanString') -and (-not $RepetitionIntervalTimeSpanString.Contains(""))) {
                $param.RepetitionInterval = ConvertToTimeSpan -TimeSpanString $RepetitionIntervalTimeSpanString
            }

            if ($PSBoundParameters.ContainsKey('RepetitionDurationTimeSpanString') -and (-not $RepetitionDurationTimeSpanString.Contains(""))) {
                $param.RepetitionDuration = ConvertToTimeSpan -TimeSpanString $RepetitionDurationTimeSpanString
            }
        }

        # ExecutionTimelimit param
        Write-Verbose $PSBoundParameters.ContainsKey("ExecuteTimeLimitTicks")
        if ($PSBoundParameters.ContainsKey("ExecuteTimeLimitTicks")) { $param.ExecutionTimeLimit = [TimeSpan]::FromTicks($ExecuteTimeLimitTicks) }

        # obtain other param
        @(
            'TaskName',
            'Description', 
            'Execute', 
            'Argument', 
            'WorkingDirectory', 
            'Runlevel',
            'Compatibility',
            'Hidden',
            'Disable', 
            'ScheduledAt'
        ) `
            | where {$PSBoundParameters.ContainsKey($_)} `
            | % { $param.$_ = Get-Variable -Name $_ -ValueOnly }
    }

    # Test current ScheduledTask
    $taskResult = TestScheduledTaskStatus @param

    # ensure check
    $ensureResult = if (($taskResult.GetEnumerator() | % {$_.Value.result}) -contains $false) {
        [Grani_ScheduleTask.EnsureType]::Absent
    }
    else {
        [Grani_ScheduleTask.EnsureType]::Present
    }

    # return hashtable
    $returnHash = [ordered]@{}
    $returnHash.Ensure = $ensureResult
    @(
        # root
        'TaskName',
        'TaskPath',
        'Description', 

        # Action
        'Execute', 
        'Argument', 
        'WorkingDirectory', 

        # Principal
        'Runlevel',

        # settings
        'Compatibility',
        'Hidden',
        'Disable',

        # Trigger
        'ScheduledAt',
        'Daily',
        'Once',
        'AtStartup',
        'AtLogOn',
        'AtLogOnUserId'
    ) `
        | where {$taskResult."$_".target -ne $null} `
        | % {$returnHash.$_ = $taskResult."$_".target}

    # convert credential to CIM Instance
    if (($PSBoundParameters.ContainsKey("Credential"))) {
        $returnHash.Credential = New-CimInstance -ClassName MSFT_Credential -Property @{Username = [string]$Credential.UserName; Password = [string]$null} -Namespace root/microsoft/windows/desiredstateconfiguration -ClientOnly
    }

    # convert timespan to string
    if (($PSBoundParameters.ContainsKey("ExecuteTimeLimitTicks")) -and ($taskResult.ExecutionTimeLimit.target.Ticks -ne $null)) {
        $returnHash.ExecuteTimeLimitTicks = [System.Int64]$taskResult.ExecutionTimeLimit.target.Ticks
    }
    if ($PSBoundParameters.ContainsKey("RepetitionIntervalTimeSpanString")) {
        $returnHash.RepetitionIntervalTimeSpanString = @($taskResult.RepetitionInterval.target | where {$_} | % {$_.ToString()})
    }
    if ($PSBoundParameters.ContainsKey("RepetitionDurationTimeSpanString")) {
        $returnHash.RepetitionDurationTimeSpanString = @($taskResult.RepetitionDuration.target | where {$_} | % {$_.ToString()})
    }

    return $returnHash
}

function Set-TargetResource {
    [OutputType([Void])]
    [CmdletBinding(DefaultParameterSetName = "Repetition")]
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateSet("Present", "Absent")]
        [System.String]$Ensure,

        [parameter(Mandatory = $true)]
        [System.String]$TaskName,

        [parameter(Mandatory = $false)]
        [System.String]$TaskPath = "\",

        [parameter(Mandatory = $false)]
        [System.String]$Description,

        [parameter(Mandatory = $false)]
        [System.String]$Execute = [string]::Empty,

        [parameter(Mandatory = $false)]
        [System.String]$Argument = [string]::Empty,

        [parameter(Mandatory = $false)]
        [System.String]$WorkingDirectory = [string]::Empty,

        [parameter(Mandatory = $false)]
        [System.Management.Automation.PSCredential]$Credential = [PSCredential]::Empty,

        [parameter(Mandatory = $false)]
        [ValidateSet("Highest", "Limited")]
        [System.String]$Runlevel = "Limited",

        [parameter(Mandatory = $false)]
        [ValidateSet("At", "Win8", "Win7", "Vista", "V1")]
        [System.String]$Compatibility = "Win8",

        [parameter(Mandatory = $false)]
        [System.Int64]$ExecuteTimeLimitTicks = [TimeSpan]::FromDays(3).Ticks,

        [parameter(Mandatory = $false)]
        [System.Boolean]$Hidden = $true,

        [parameter(Mandatory = $false)]
        [System.Boolean]$Disable = $true,

        [parameter(Mandatory = $false)]
        [System.DateTime[]]$ScheduledAt,

        [parameter(Mandatory = $false, parameterSetName = "Repetition")]
        [string[]]$RepetitionIntervalTimeSpanString = @([TimeSpan]::FromHours(1).ToString()),

        [parameter(Mandatory = $false, parameterSetName = "Repetition")]
        [string[]]$RepetitionDurationTimeSpanString = @([TimeSpan]::MaxValue.ToString()),

        [parameter(Mandatory = $false, parameterSetName = "Daily")]
        [System.Boolean]$Daily = $false,

        [parameter(Mandatory = $false, parameterSetName = "Once")]
        [System.Boolean]$Once,

        [parameter(Mandatory = $false, parameterSetName = "AtStartup")]
        [System.Boolean]$AtStartup,

        [parameter(Mandatory = $false, parameterSetName = "AtLogOn")]
        [System.Boolean]$AtLogOn,

        [parameter(Mandatory = $false, parameterSetName = "AtLogOn")]
        [System.String]$AtLogOnUserId = ""
    )
    
    # Task Path validation
    $validatedTaskPath = ValidateTaskPathLastChar -taskPath $TaskPath
    $newTaskPath = ValidateTaskPathFirstChar -taskPath $validatedTaskPath

    # Get Existing Schedule Task
    $existingTaskParam = 
    @{
        TaskName = $taskName
        TaskPath = $newTaskPath
    }
    $existingTask = GetExistingTaskScheduler @existingTaskParam

    #region Absent

    if ($Ensure -eq "Absent") {
        Write-Verbose ($verboseMessages.EnsureDetectAbsent -f $newTaskPath, $TaskName)
        $existingTask | Unregister-ScheduledTask -Confirm:$false > $null;
        RemoveScheduledTaskEmptyDirectoryPath
        return;
    }

    #endregion

    #region Present

    Write-Verbose ($verboseMessages.EnsureDetectPresent -f $newTaskPath, $TaskName)
    
    # Enable/Disable
    if (($existingTask | measure).Count -ne 0) {
        Write-Debug ($debugMessages.SetDisable -f $Disable)
        switch ($Disable) {
            $true {
                Write-Verbose ($verboseMessages.DisableDetected -f $newTaskPath, $TaskName)
                $existingTask | Disable-ScheduledTask
                return
            }
            $false {
                $existingTask | Enable-ScheduledTask
            }
        }
    }

    # validation
    ValidateSameFolderNotExist @existingTaskParam

    $scheduleTaskParam = @{}

    # description
    if (-not [string]::IsNullOrWhiteSpace($Description)) {
        Write-Debug ($debugMessages.SetDescription -f $Description)
        $scheduleTaskParam.description = $Description
    }

    # action
    Write-Debug ($debugMessages.SetAction -f $Execute, $Argument, $WorkingDirectory)
    $actionParam = 
    @{
        Execute          = $Execute
        Argument         = $Argument
        WorkingDirectory = $WorkingDirectory
    }
    $scheduleTaskParam.action = CreateTaskSchedulerAction @actionParam

    # trigger
    if ($ScheduledAt -ne $null) {
        if ($Daily -or $Once) {
            $interval = $duration = $null
        }
        elseif (($null -eq $RepetitionIntervalTimeSpanString) -and ($null -eq $RepetitionDurationTimeSpanString)) {
            $interval = $duration = $null
        }
        elseif ((-not $RepetitionIntervalTimeSpanString.Contains("")) -and (-not $RepetitionDurationTimeSpanString.Contains(""))) {
            $interval = ConvertToTimeSpan -TimeSpanString $RepetitionIntervalTimeSpanString
            $duration = ConvertToTimeSpan -TimeSpanString $RepetitionDurationTimeSpanString
        }
        else {
            $interval = $duration = $null
        }
    }
    elseif ($AtStartup -or $AtLogOn) {
        # Both AtStartup and $AtLogOn cannot use with $ScheduledAt
        $interval = $duration = $ScheduledAt = $null
    }

    Write-Debug ($debugMessages.SetTrigger -f $interval, $duration, $ScheduledAt, $Daily, $Once, $AtStartup, $AtLogOn)
    $triggerParam =
    @{
        RepetitionInterval = $interval
        RepetitionDuration = $duration
        ScheduledAt        = $ScheduledAt
        Daily              = $Daily
        Once               = $Once
        AtStartup          = $AtStartup
        AtLogOn            = $AtLogOn
        LogOnUserId        = $AtLogOnUserId
    }
    $scheduleTaskParam.trigger = CreateTaskSchedulerTrigger @triggerParam

    # settings
    $scheduleTaskParam.settings = if ($PSBoundParameters.ContainsKey('ExecuteTimeLimitTicks')) {
        New-ScheduledTaskSettingsSet -Disable:$Disable -Hidden:$Hidden -Compatibility $Compatibility -ExecutionTimeLimit (TicksToTimeSpan -Ticks $ExecuteTimeLimitTicks)
    }
    else {
        New-ScheduledTaskSettingsSet -Disable:$Disable -Hidden:$Hidden -Compatibility $Compatibility
    }

    # Register ScheduledTask
    $registerParam = GetRegisterParam -Credential $Credential -Runlevel $Runlevel -TaskName $TaskName -TaskPath $newTaskPath -scheduleTaskParam $scheduleTaskParam
    Register-ScheduledTask @registerParam -Force | select * | Out-String | Write-Debug

    #endregion
}

function Test-TargetResource {
    [OutputType([Bool])]
    [CmdletBinding(DefaultParameterSetName = "Repetition")]
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateSet("Present", "Absent")]
        [System.String]$Ensure,

        [parameter(Mandatory = $true)]
        [System.String]$TaskName,

        [parameter(Mandatory = $false)]
        [System.String]$TaskPath = "\",

        [parameter(Mandatory = $false)]
        [System.String]$Description,

        [parameter(Mandatory = $false)]
        [System.String]$Execute,

        [parameter(Mandatory = $false)]
        [System.String]$Argument,

        [parameter(Mandatory = $false)]
        [System.String]$WorkingDirectory,

        [parameter(Mandatory = $false)]
        [System.Management.Automation.PSCredential]$Credential = [PSCredential]::Empty,

        [parameter(Mandatory = $false)]
        [ValidateSet("Highest", "Limited")]
        [System.String]$Runlevel,

        [parameter(Mandatory = $false)]
        [ValidateSet("At", "Win8", "Win7", "Vista", "V1")]
        [System.String]$Compatibility,

        [parameter(Mandatory = $false)]
        [System.Int64]$ExecuteTimeLimitTicks = [TimeSpan]::FromDays(3).Ticks,

        [parameter(Mandatory = $false)]
        [System.Boolean]$Hidden,

        [parameter(Mandatory = $false)]
        [System.Boolean]$Disable = $true,

        [parameter(Mandatory = $false)]
        [System.DateTime[]]$ScheduledAt,

        [parameter(Mandatory = $false, parameterSetName = "Repetition")]
        [string[]]$RepetitionIntervalTimeSpanString = @([TimeSpan]::FromHours(1).ToString()),

        [parameter(Mandatory = $false, parameterSetName = "Repetition")]
        [string[]]$RepetitionDurationTimeSpanString = @([TimeSpan]::MaxValue.ToString()),

        [parameter(Mandatory = $false, parameterSetName = "Daily")]
        [System.Boolean]$Daily,

        [parameter(Mandatory = $false, parameterSetName = "Once")]
        [System.Boolean]$Once,

        [parameter(Mandatory = $false, parameterSetName = "AtStartup")]
        [System.Boolean]$AtStartup,

        [parameter(Mandatory = $false, parameterSetName = "AtLogOn")]
        [System.Boolean]$AtLogOn,

        [parameter(Mandatory = $false, parameterSetName = "AtLogOn")]
        [System.String]$AtLogOnUserId = ""
    )

    $param = @{}

    # obtain other param
    @(
        'Ensure',
        'TaskName',
        'TaskPath'
        'Description', 
        'Execute', 
        'Argument', 
        'WorkingDirectory', 
        'Credential', 
        'Runlevel',
        'Compatibility',
        'ExecuteTimeLimitTicks',
        'Hidden',
        'Disable', 
        'ScheduledAt',
        'RepetitionIntervalTimeSpanString',
        'RepetitionDurationTimeSpanString',
        'Daily',
        'Once',
        'AtStartup',
        'AtLogOn',
        'AtLogOnUserId'
    ) `
        | where {$PSBoundParameters.ContainsKey($_)} `
        | % { $param.$_ = Get-Variable -Name $_ -ValueOnly }
    return (Get-TargetResource @param).Ensure -eq $Ensure
}

#endregion

#region Validate Helper

function ValidateTaskPathLastChar {
    [OutputType([Void])]
    [CmdletBinding()]
    param
    (
        [string]$TaskPath
    )

    $newTaskPath = $TaskPath;
    $lastChar = [System.Linq.Enumerable]::ToArray($TaskPath) | select -Last 1
    if ($lastChar -ne "\") {
        $newTaskPath = $TaskPath + "\";
        Write-Debug ($debugMessages.ValidateTaskPathChar -f "Last", $TaskPath, $newTaskPath);
    }
    return $newTaskPath
}

function ValidateTaskPathFirstChar {
    [OutputType([Void])]
    [CmdletBinding()]
    param
    (
        [string]$TaskPath
    )
    
    $newTaskPath = $TaskPath;
    $firstChar = [System.Linq.Enumerable]::ToArray($TaskPath) | select -First 1;
    if ($firstChar -ne "\") {
        $newTaskPath = "\" + $TaskPath;
        Write-Debug ($debugMessages.ValidateTaskPathChar -f "First", $TaskPath, $newTaskPath);
    }
    return $newTaskPath
}

function ValidateSameFolderNotExist {
    [OutputType([Void])]
    [CmdletBinding()]
    param
    (
        [string]$TaskName,
        [string]$TaskPath
    )

    if (TestExistingTaskSchedulerWithPath -TaskName $TaskName -TaskPath $TaskPath) { throw New-Object System.InvalidOperationException ($errorMessages.SameNameFolderFound -f $taskName) }
}

#endregion

#region Create Helper

function CreateTaskSchedulerAction {
    [CmdletBinding()]
    param
    (
        [string]$Argument,
        [string]$Execute,
        [string]$WorkingDirectory
    )
    if ($Execute -eq [string]::Empty) { throw New-Object System.InvalidOperationException ($errorMessages.ExecuteBrank) }

    $param = @{}
    $param.Execute = $Execute
    if ($Argument -ne [string]::Empty) { $param.Argument = $Argument }
    if ($WorkingDirectory -ne [string]::Empty) { $param.WorkingDirectory = $WorkingDirectory }
    return New-ScheduledTaskAction @param
}

function ConvertToTimeSpan {
    [OutputType([TimeSpan[]])]
    [CmdletBinding()]
    param(
        [parameter(Mandatory = $false, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string[]]$TimeSpanString
    )

    foreach ($x in $TimeSpanString) {
        if (-not [string]::IsNullOrWhiteSpace($TimeSpanString)) {
            [TimeSpan]$result = New-Object System.TimeSpan;
            if (![TimeSpan]::TryParse($x, [ref]$result)) {
                [TimeSpan]::MaxValue
            }
            else {
                $result
            }
        }
    }
}

function CreateTaskSchedulerTrigger {
    [CmdletBinding()]
    param
    (
        [TimeSpan[]]$RepetitionInterval,
        [TimeSpan[]]$RepetitionDuration,
        [DateTime[]]$ScheduledAt,
        [bool]$Daily,
        [bool]$Once,
        [bool]$AtStartup,
        [bool]$AtLogOn,
        [string]$LogOnUserId
    )

    $trigger = if ((!$Daily) -and (!$Once) -and (!$AtStartup) -and (!$AtLogOn)) {
        $repetitionPair = New-ZipPairs -first $RepetitionInterval -Second $RepetitionDuration
        $ScheduledAtPair = New-ZipPairs -first $ScheduledAt -Second $repetitionPair
        $ScheduledAtPair `
            | % {
            $at = $_.Item1;
            $interval = $_.Item2.Item1;
            $duration = $_.Item2.Item2;
            if ($interval -ge $duration) { throw New-Object System.InvalidOperationException ($errorMessages.InvalidTrigger -f $interval, $duration)}

            # if TimeSpan.MaxValue should lower than uint16.MaxValue == 65535
            if ($duration.TotalDays -ge [System.UInt16]::MaxValue) {
                try {
                    # set as infinite for Win2016/Win10 or higher
                    $t = New-ScheduledTaskTrigger -At $at -RepetitionInterval $interval -Once;
                    # only Windows 10 or Windows 2016 have this property.
                    if (($t.Repetition | Get-Member -MemberType Properties | Where Name -eq "StopAtDurationEnd" | Measure).Count -eq 1) {
                        $t.Repetition.StopAtDurationEnd = $false;
                    }
                    $t;
                }
                catch [System.Management.Automation.PSArgumentException] {
                    # set for Windows Server 2012 R2, 8.1 or lower
                    New-ScheduledTaskTrigger -At $at -RepetitionInterval $interval -RepetitionDuration $duration -Once
                }
            }
            else {
                New-ScheduledTaskTrigger -At $at -RepetitionInterval $interval -RepetitionDuration $duration -Once
            }
        }
    }
    elseif ($Daily) {
        $ScheduledAt | % {New-ScheduledTaskTrigger -At $_ -Daily}
    }
    elseif ($Once) {
        $ScheduledAt | % {New-ScheduledTaskTrigger -At $_ -Once}
    }
    elseif ($AtStartup) {
        New-ScheduledTaskTrigger -AtStartup
    }
    elseif ($AtLogOn) {
        if (-not ([string]::IsNullOrWhiteSpace($LogOnUserId))) {
            New-ScheduledTaskTrigger -AtLogOn -User $LogOnUserId
        }
        else {
            New-ScheduledTaskTrigger -AtLogOn
        }
        
    }
    return $trigger
}

#endregion

#region Convert Helper

function TicksToTimeSpan {
    [OutputType([System.TimeSpan])]
    [CmdletBinding()]
    param
    (
        [System.Int64]$Ticks
    )
    return [TimeSpan]::FromTicks($Ticks)
}

#endregion

#region Get Helper

function GetExistingTaskScheduler {
    [CmdletBinding()]
    param
    (
        [string]$TaskName,
        [string]$TaskPath
    )
    return Get-ScheduledTask | where TaskName -eq $TaskName | where TaskPath -eq $TaskPath
}

function GetRegisterParam {
    [OutputType([HashTable])]
    [CmdletBinding()]
    param
    (
        $Credential,
        $Runlevel,
        [string]$TaskName,
        [string]$TaskPath,
        $scheduleTaskParam
    )

    if (([PSCredential]::Empty -ne $Credential) -and ("SYSTEM" -ne $Credential.UserName)) {
        Write-Debug $debugMessages.UsePrincipal
        # Principal
        $principalParam = 
        @{
            UserId    = $Credential.UserName
            RunLevel  = $Runlevel
            LogOnType = "InteractiveOrPassword"
        }
        $scheduleTaskParam.principal = New-ScheduledTaskPrincipal @principalParam

        # return
        return @{
            InputObject = New-ScheduledTask @scheduleTaskParam
            TaskName    = $TaskName
            TaskPath    = $TaskPath
            User        = $Credential.UserName
            Password    = $Credential.GetNetworkCredential().Password
        }
    }
    else {
        Write-Debug $debugMessages.SkipPrincipal
        $principalParam = 
        @{
            Id                  = "Author"
            UserId              = "SYSTEM"
            RunLevel            = $Runlevel
            LogOnType           = "ServiceAccount"
            ProcessTokenSidType = "Default"
        }
        $scheduleTaskParam.principal = New-ScheduledTaskPrincipal @principalParam 

        # return
        return @{
            InputObject = New-ScheduledTask @scheduleTaskParam
            TaskName    = $TaskName
            TaskPath    = $TaskPath
        }
    }
}

#endregion

#region Test Helper

function TestExistingTaskScheduler {
    [OutputType([bool])]
    [CmdletBinding()]
    param
    (
        [string]$TaskName,
        [string]$TaskPath
    )

    $task = GetExistingTaskScheduler -TaskName $TaskName -TaskPath $TaskPath
    return ($task | Measure-Object).count -ne 0
}

function TestExistingTaskSchedulerWithPath {
    [OutputType([bool])]
    [CmdletBinding()]
    param
    (
        [string]$TaskName,
        [string]$TaskPath
    )

    if ($TaskPath -ne "\") { return $false }

    # only run when taskpath is \
    $path = Join-Path $env:windir "System32\Tasks"
    $result = Get-ChildItem -Path $path -Directory | where Name -eq $TaskName

    if (($result | measure).count -ne 0) {
        return $true
    }
    return $false
}

function GetScheduledTask {
    [OutputType([HashTable])]
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [Microsoft.Management.Infrastructure.CimInstance[]]$ScheduledTask,

        [parameter(Mandatory = $true)]
        [string]$Parameter,

        [parameter(Mandatory = $true)]
        [string]$Value
    )

    Write-Debug ($debugMessages.CheckScheduleTaskExist -f $parameter, $Value)
    $task = $ScheduledTask | where $Parameter -eq $Value
    $uniqueValue = $task.$Parameter | sort -Unique
    $result = $uniqueValue -eq $Value
    Write-Debug ($debugMessages.ScheduleTaskResult -f $Parameter, $result, $uniqueValue)
    return @{
        task   = $task
        target = $uniqueValue
        result = $result
    }
}

function TestScheduledTask {
    [OutputType([bool])]
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [Microsoft.Management.Infrastructure.CimInstance]$ScheduledTask,

        [parameter(Mandatory = $true)]
        [Grani_ScheduleTask.ScheduledParameterType]$Type,

        [parameter(Mandatory = $true)]
        [string]$Parameter,

        [parameter(Mandatory = $false)]
        [PSObject]$Value,

        [bool]$IsExist
    )

    # skip when Parameter not use
    if (!$IsExist) {
        Write-Debug ($debugMessages.SkipNoneUseParameter -f $Parameter)
        return @{
            target = $null
            result = $true
        }
    }

    # skip null
    if ($Value -eq $null) {
        Write-Debug ($debugMessages.SkipNullPassedParameter -f $Parameter)
        return @{
            target = $null
            result = $true
        }
    }

    Write-Debug ($debugMessages.CheckScheduleTaskParameter -f $Parameter, $Value)
    $target = switch ($Type) {
        ([Grani_ScheduleTask.ScheduledParameterType]::Root) {
            $ScheduledTask.$Parameter | sort -Unique
        }
        ([Grani_ScheduleTask.ScheduledParameterType]::Actions) {
            $ScheduledTask.Actions.$Parameter | sort -Unique
        }
        ([Grani_ScheduleTask.ScheduledParameterType]::Principal) {
            $ScheduledTask.Principal.$Parameter | sort -Unique
        }
        ([Grani_ScheduleTask.ScheduledParameterType]::Settings) {
            $ScheduledTask.Settings.$Parameter | sort -Unique
        }
        ([Grani_ScheduleTask.ScheduledParameterType]::Triggers) {
            $ScheduledTask.Triggers.$Parameter | sort -Unique
        }
    }
            
    if ($Value.GetType().FullName -eq [string].FullName) {
        if (($target -eq $null) -and ([string]::IsNullOrEmpty($Value))) {
            return @{
                target = $target
                result = $true
            }
            Write-Debug ($debugMessages.ScheduleTaskResult -f $Parameter, $result, $target)
        }
    }

    # value check
    $result = $target -eq $Value
    Write-Debug ($debugMessages.ScheduleTaskResult -f $Parameter, $result, $target)
    return @{
        target = $target
        result = $result
    }
}

function TestScheduledTaskExecutionTimeLimit {
    [OutputType([bool])]
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [Microsoft.Management.Infrastructure.CimInstance]$ScheduledTask,

        [parameter(Mandatory = $false)]
        [PSObject]$Value,

        [bool]$IsExist
    )

    $private:parameter = "ExecutionTimeLimit"

    # skip when Parameter not use
    if (!$IsExist) {
        Write-Debug ($debugMessages.SkipNoneUseParameter -f $Parameter)
        return @{
            target = $null
            result = $true
        }
    }

    # skip null
    if ($Value -eq $null) {
        Write-Debug ($debugMessages.SkipNullPassedParameter -f $Parameter)
        return @{
            target = $null
            result = $true
        }
    }

    $Value = $Value -as [TimeSpan]
    Write-Debug ($debugMessages.CheckScheduleTaskParameterTimeSpan -f $parameter, $Value.TotalMinutes)
    $executionTimeLimitTimeSpan = [System.Xml.XmlConvert]::ToTimeSpan($ScheduledTask.Settings.$parameter)
    $result = $Value -eq $executionTimeLimitTimeSpan
    Write-Debug ($debugMessages.ScheduleTaskTimeSpanResult -f $parameter, $result, $executionTimeLimitTimeSpan.TotalMinutes)
    return @{
        target = $executionTimeLimitTimeSpan
        result = $result
    }
}

function TestScheduledTaskDisable {
    [OutputType([bool])]
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [Microsoft.Management.Infrastructure.CimInstance]$ScheduledTask,

        [parameter(Mandatory = $false)]
        [PSObject]$Value,

        [bool]$IsExist
    )

    # skip when Parameter not use
    if (!$IsExist) {
        Write-Debug ($debugMessages.SkipNoneUseParameter -f $Parameter)
        return @{
            target = $null
            result = $true
        }
    }

    # convert Enable -> Disable
    $target = $ScheduledTask.Settings.Enabled -eq $false
            
    # value check
    Write-Debug ($debugMessages.CheckScheduleTaskParameter -f "Disable", $Value)
    $result = $target -eq $Value
    Write-Debug ($debugMessages.ScheduleTaskResult -f "Disable", $result, $target)
    return @{
        target = $target
        result = $result
    }
}

function TestScheduledTaskScheduledAt {
    [OutputType([bool])]
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [Microsoft.Management.Infrastructure.CimInstance]$ScheduledTask,

        [parameter(Mandatory = $false)]
        [DateTime[]]$Value,

        [bool]$IsExist
    )

    $private:parameter = "StartBoundary"

    # skip when Parameter not use
    if (!$IsExist) {
        Write-Debug ($debugMessages.SkipNoneUseParameter -f $Parameter)
        return @{
            target = $null
            result = $true
        }
    }

    # skip null
    if ($Value -eq $null) {
        Write-Debug ($debugMessages.SkipNullPassedParameter -f $Parameter)
        return @{
            target = $null
            result = $true
        }
    }

    $valueCount = ($Value | measure).Count
    $scheduleCount = ($ScheduledTask.Triggers | measure).Count
    if ($valueCount -ne $scheduleCount) {
        throw New-Object System.ArgumentException ($errorMessages.ScheduleAtArgumentLength -f $scheduleCount, $valueCount)
    }

    $result = $target = @()
    for ($i = 0; $i -le ($ScheduledTask.Triggers.$parameter.Count - 1); $i++) {
        Write-Debug ($debugMessages.CheckScheduleTaskParameter -f $parameter, $Value[$i])
        $startBoundaryDateTime = [System.Xml.XmlConvert]::ToDateTime(@($ScheduledTask.Triggers.$parameter)[$i])
        $target += $startBoundaryDateTime
        $result += @($Value)[$i] -eq $startBoundaryDateTime
        Write-Debug ($debugMessages.ScheduleTaskResult -f $parameter, $result[$i], $startBoundaryDateTime)
    }
    return @{
        target = $target
        result = $result | sort -Unique
    }
}

function TestScheduledTaskScheduledRepetitionInterval {
    [OutputType([bool])]
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [Microsoft.Management.Infrastructure.CimInstance]$ScheduledTask,

        [parameter(Mandatory = $false)]
        [TimeSpan[]]$Value,

        [bool]$IsExist
    )

    # skip when Parameter not use
    if (!$IsExist) {
        Write-Debug ($debugMessages.SkipNoneUseParameter -f "Interval")
        return @{
            target = $null
            result = $true
        }
    }

    # skip null
    if ($Value -eq $null) {
        Write-Debug ($debugMessages.SkipNullPassedParameter -f "Interval")
        return @{
            target = $null
            result = $true
        }
    }

    $valueCount = ($Value | measure).Count
    $scheduleCount = ($ScheduledTask.Triggers | measure).Count
    if ($valueCount -ne $scheduleCount) {
        throw New-Object System.ArgumentException ($errorMessages.ScheduleRepetitionArgumentLength -f $scheduleCount, $valueCount)
    }

    $result = $target = @()
    for ($i = 0; $i -le ($ScheduledTask.Triggers.Repetition.Interval.Count - 1); $i++) {
        Write-Debug ($debugMessages.CheckScheduleTaskParameter -f "Interval", $Value[$i])
        $repetition = [System.Xml.XmlConvert]::ToTimeSpan(@($ScheduledTask.Triggers.Repetition.Interval)[$i])
        $target += $repetition
        $result = @($Value)[$i] -eq $repetition
        Write-Debug ($debugMessages.ScheduleTaskResult -f "Interval", $result[$i], $target.TotalMinutes)
    }
    return @{
        target = $target
        result = $result | sort -Unique
    }
}

function TestScheduledTaskScheduledRepetitionDuration {
    [OutputType([bool])]
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [Microsoft.Management.Infrastructure.CimInstance]$ScheduledTask,

        [parameter(Mandatory = $false)]
        [TimeSpan[]]$Value,

        [bool]$IsExist
    )

    # skip when Parameter not use
    if (!$IsExist) {
        Write-Debug ($debugMessages.SkipNoneUseParameter -f "Duration")
        return @{
            target = $null
            result = $true
        }
    }

    # skip null
    if ($Value -eq $null) {
        Write-Debug ($debugMessages.SkipNullPassedParameter -f "Duration")
        return @{
            target = $null
            result = $true
        }
    }

    $valueCount = ($Value | measure).Count
    $scheduleCount = ($ScheduledTask.Triggers | measure).Count
    if ($valueCount -ne $scheduleCount) {
        throw New-Object System.ArgumentException ($errorMessages.ScheduleRepetitionArgumentLength -f $scheduleCount, $valueCount)
    }

    $result = $target = @()
    for ($i = 0; $i -le ($ScheduledTask.Triggers.Repetition.Duration.Count - 1); $i++) {
        Write-Debug ($debugMessages.CheckScheduleTaskParameter -f "Duration", $Value[$i])
        $repetition = [System.Xml.XmlConvert]::ToTimeSpan(@($ScheduledTask.Triggers.Repetition.Duration)[$i])
        $target += $repetition
        $result = @($Value)[$i] -eq $repetition
        Write-Debug ($debugMessages.ScheduleTaskResult -f "Duration", $result[$i], $target.TotalMinutes)
    }
    return @{
        target = $target
        result = $result | sort -Unique
    }
}

function TestScheduledTaskTriggerScheduleByDay {
    [OutputType([bool])]
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [System.Xml.XmlDocument]$ScheduledTaskXml,

        [parameter(Mandatory = $true)]
        [string]$Parameter,

        [parameter(Mandatory = $false)]
        [PSObject]$Value,

        [bool]$IsExist
    )

    # skip when Parameter not use
    if (!$IsExist) {
        Write-Debug ($debugMessages.SkipNoneUseParameter -f $Parameter)
        return @{
            target = $null
            result = $true
        }
    }

    $trigger = ($ScheduledTaskXml.task.Triggers.CalendarTrigger.ScheduleByDay | measure).Count
    $result = $false
    switch ($Parameter) {
        "Daily" {
            Write-Debug $debugMessages.CheckSchedulerDaily
            $result = if ($Value) {
                $trigger -ne 0
            }
            else {
                $trigger -eq 0
            }
            Write-Debug ($debugMessages.ScheduleTaskResult -f $Parameter, $result, $trigger)
        }
        "Once" {
            Write-Debug $debugMessages.CheckSchedulerOnce
            $result = if ($Value) {
                $trigger -eq 0
            }
            else {
                $trigger -ne 0
            }
            Write-Debug ($debugMessages.ScheduleTaskResult -f $Parameter, $result, $trigger)
        }
    }
    return @{
        target = $result
        result = $result
    }
}

function TestScheduledTaskTriggerBootTrigger {
    [OutputType([bool])]
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [System.Xml.XmlDocument]$ScheduledTaskXml,

        [parameter(Mandatory = $true)]
        [string]$Parameter,

        [parameter(Mandatory = $false)]
        [PSObject]$Value,

        [bool]$IsExist
    )

    # skip when Parameter not use
    if (!$IsExist) {
        Write-Debug ($debugMessages.SkipNoneUseParameter -f $Parameter)
        return @{
            target = $null
            result = $true
        }
    }

    $trigger = $ScheduledTaskXml.task.Triggers.BootTrigger
    $result = $false
    Write-Debug $debugMessages.CheckSchedulerAtStartup
    $target = $trigger.Enabled
    $result = $target -eq $Value
    Write-Debug ($debugMessages.ScheduleTaskResult -f $Parameter, $result, $target)
    return @{
        target = $target
        result = $result
    }
}

function TestScheduledTaskTriggerAtLogonTrigger {
    [OutputType([bool])]
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [System.Xml.XmlDocument]$ScheduledTaskXml,

        [parameter(Mandatory = $true)]
        [string]$Parameter,

        [parameter(Mandatory = $false)]
        [PSObject]$Value,

        [bool]$IsExist
    )

    # skip when Parameter not use
    if (!$IsExist) {
        Write-Debug ($debugMessages.SkipNoneUseParameter -f $Parameter)
        return @{
            target = $null
            result = $true
        }
    }

    $trigger = $ScheduledTaskXml.task.Triggers.LogonTrigger
    $result = $false
    Write-Debug $debugMessages.CheckSchedulerAtLogOn
    if ([string]::IsNullOrEmpty($trigger)) {
        Write-Debug 'Detected $trigger is null. Fall back to xml check.'
        $target = ($ScheduledTaskXml.task.Triggers | Get-Member -MemberType Properties | Where Name -eq "LogonTrigger" | Measure).Count -ge 1
        $result = $target -eq $Value
        Write-Debug ($debugMessages.ScheduleTaskResult -f $Parameter, $result, $target)
    }
    else {
        $target = $null -ne $trigger
        $result = $target -eq $Value
        Write-Debug ($debugMessages.ScheduleTaskResult -f $Parameter, $result, $target)
    }

    return @{
        target = $target
        result = $result
    }
}

function TestScheduledTaskTriggerUserIdLogonTrigger {
    [OutputType([bool])]
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [System.Xml.XmlDocument]$ScheduledTaskXml,

        [parameter(Mandatory = $true)]
        [string]$Parameter,

        [parameter(Mandatory = $false)]
        [PSObject]$Value,

        [bool]$IsExist
    )

    # skip when Parameter not use
    if (!$IsExist) {
        Write-Debug ($debugMessages.SkipNoneUseParameter -f $Parameter)
        return @{
            target = $null
            result = $true
        }
    }

    $trigger = $ScheduledTaskXml.task.Triggers.LogonTrigger
    $result = $false
    if ($value -eq "") { $value = $null }
    Write-Debug ($debugMessages.CheckSchedulerUserId -f $Value)
    $target = $trigger.UserId
    $result = $target -eq $Value
    Write-Debug ($debugMessages.ScheduleTaskResult -f $Parameter, $result, $target)

    return @{
        target = $target
        result = $result
    }
}

function TestScheduledTaskStatus {
    [OutputType([HashTable])]
    [CmdletBinding(DefaultParameterSetName = "Repetition")]
    param
    (
        [parameter(Mandatory = 1, Position = 0)]
        [string]$TaskName,
    
        [parameter(Mandatory = 0, Position = 1)]
        [string]$TaskPath = "\",

        [parameter(Mandatory = 0, Position = 2)]
        [string]$Execute,

        [parameter(Mandatory = 0, Position = 3)]
        [string]$Argument,
    
        [parameter(Mandatory = 0, Position = 4)]
        [string]$WorkingDirectory,

        [parameter(Mandatory = 0, Position = 5)]
        [datetime[]]$ScheduledAt,

        [parameter(Mandatory = 0, Position = 6, parameterSetName = "Repetition")]
        [TimeSpan[]]$RepetitionInterval,

        [parameter(Mandatory = 0, Position = 7, parameterSetName = "Repetition")]
        [TimeSpan[]]$RepetitionDuration,

        [parameter(Mandatory = 0, Position = 8, parameterSetName = "Daily")]
        [bool]$Daily = $false,

        [parameter(Mandatory = 0, Position = 9, parameterSetName = "Once")]
        [bool]$Once = $false,

        [parameter(Mandatory = 0, Position = 10, parameterSetName = "AtStatup")]
        [bool]$AtStartup = $false,

        [parameter(Mandatory = 0, Position = 11, parameterSetName = "AtLogOn")]
        [bool]$AtLogOn = $false,

        [parameter(Mandatory = 0, Position = 12, parameterSetName = "AtLogOn")]
        [string]$AtLogOnUserId = "",

        [parameter(Mandatory = 0, Position = 13)]
        [string]$Description,

        [parameter(Mandatory = 0, Position = 14)]
        [PScredential]$Credential,

        [parameter(Mandatory = 0, Position = 15)]
        [bool]$Disable,

        [parameter(Mandatory = 0, Position = 16)]
        [bool]$Hidden,

        [parameter(Mandatory = 0, Position = 17)]
        [TimeSpan]$ExecutionTimeLimit,

        [parameter(Mandatory = 0, Position = 18)]
        [ValidateSet("At", "Win8", "Win7", "Vista", "V1")]
        [string]$Compatibility,

        [parameter(Mandatory = 0, Position = 19)]
        [ValidateSet("Highest", "Limited")]
        [string]$Runlevel
    )

    #region Root

    $returnHash = [ordered]@{}

    # get whole task
    $root = Get-ScheduledTask

    # TaskPath
    $returnHash.TaskPath = GetScheduledTask -ScheduledTask $root -Parameter TaskPath -Value $TaskPath
    if ($null -eq $returnHash.TaskPath.task) {
        foreach ($item in [Enum]::GetNames([Grani_ScheduleTask.ScheduledTaskPropertyType])) {
            $returnHash.$item = @{target = $null; result = $true}
        }
        return $returnHash;
    }

    # TaskName
    $returnHash.TaskName = GetScheduledTask -ScheduledTask $returnHash.TaskPath.task -Parameter Taskname -Value $TaskName

    # default
    $current = $returnHash.TaskName.task
    if (($current | measure).Count -eq 0) { return $returnHash }

    # export as xml
    [xml]$script:xml = Export-ScheduledTask -TaskName $current.TaskName -TaskPath $current.TaskPath

    # Description
    $returnHash.Description = TestScheduledTask -ScheduledTask $current -Parameter Description -Value $Description -Type ([Grani_ScheduleTask.ScheduledParameterType]::Root) -IsExist ($PSBoundParameters.ContainsKey('Description'))

    #endregion

    #region Action

    # Execute
    $returnHash.Execute = TestScheduledTask -ScheduledTask $current -Parameter Execute -Value $Execute -Type ([Grani_ScheduleTask.ScheduledParameterType]::Actions) -IsExist ($PSBoundParameters.ContainsKey('Execute'))

    # Arguments
    $returnHash.Argument = TestScheduledTask -ScheduledTask $current -Parameter Arguments -Value $Argument -Type ([Grani_ScheduleTask.ScheduledParameterType]::Actions) -IsExist ($PSBoundParameters.ContainsKey('Argument'))

    # WorkingDirectory
    $returnHash.WorkingDirectory = TestScheduledTask -ScheduledTask $current -Parameter WorkingDirectory -Value $WorkingDirectory -Type ([Grani_ScheduleTask.ScheduledParameterType]::Actions) -IsExist ($PSBoundParameters.ContainsKey('WorkingDirectory'))

    #endregion

    #region Principal

    # UserId
    $returnHash.Credential = TestScheduledTask -ScheduledTask $current -Parameter UserId -Value $Credential.UserName -Type ([Grani_ScheduleTask.ScheduledParameterType]::Principal) -IsExist ($PSBoundParameters.ContainsKey('Credential'))

    # RunLevel
    $returnHash.RunLevel = TestScheduledTask -ScheduledTask $current -Parameter RunLevel -Value $Runlevel -Type ([Grani_ScheduleTask.ScheduledParameterType]::Principal) -IsExist ($PSBoundParameters.ContainsKey('Runlevel'))

    #endregion

    #region Settings

    # Compatibility
    $returnHash.Compatibility = TestScheduledTask -ScheduledTask $current -Parameter Compatibility -Value $Compatibility -Type ([Grani_ScheduleTask.ScheduledParameterType]::Settings) -IsExist ($PSBoundParameters.ContainsKey('Compatibility'))

    # ExecutionTimeLimit
    $returnHash.ExecutionTimeLimit = TestScheduledTaskExecutionTimeLimit -ScheduledTask $current -Value $ExecutionTimeLimit -IsExist ($PSBoundParameters.ContainsKey('ExecutionTimeLimit'))

    # Hidden
    $returnHash.Hidden = TestScheduledTask -ScheduledTask $current -Parameter Hidden -Value $Hidden -Type ([Grani_ScheduleTask.ScheduledParameterType]::Settings) -IsExist ($PSBoundParameters.ContainsKey('Hidden'))

    # Disable
    $returnHash.Disable = TestScheduledTaskDisable -ScheduledTask $current -Value $Disable -IsExist ($PSBoundParameters.ContainsKey('Disable'))

    #endregion

    #region Triggers

    # SchduledAt
    $returnHash.ScheduledAt = TestScheduledTaskScheduledAt -ScheduledTask $current -Value $ScheduledAt -IsExist ($PSBoundParameters.ContainsKey('ScheduledAt'))

    # RepetitionInterval
    $returnHash.RepetitionInterval = TestScheduledTaskScheduledRepetitionInterval -ScheduledTask $current -Value $RepetitionInterval -IsExist ($PSBoundParameters.ContainsKey('RepetitionInterval'))

    # RepetitionDuration
    $returnHash.RepetitionDuration = TestScheduledTaskScheduledRepetitionDuration -ScheduledTask $current -Value $RepetitionDuration -IsExist ($PSBoundParameters.ContainsKey('RepetitionDuration'))

    # Daily
    $returnHash.Daily = TestScheduledTaskTriggerScheduleByDay -ScheduledTaskXml $xml -Parameter Daily -Value $Daily -IsExist ($PSBoundParameters.ContainsKey('Daily'))

    # Once
    $returnHash.Once = TestScheduledTaskTriggerScheduleByDay -ScheduledTaskXml $xml -Parameter Once -Value $Once -IsExist ($PSBoundParameters.ContainsKey('Once'))

    # AtStartup
    $returnHash.AtStartup = TestScheduledTaskTriggerBootTrigger -ScheduledTaskXml $xml -Parameter AtStatup -Value $AtStartup -IsExist ($PSBoundParameters.ContainsKey('AtStartup'))

    # AtLogOn
    $returnHash.AtLogOn = TestScheduledTaskTriggerAtLogonTrigger -ScheduledTaskXml $xml -Parameter AtLogOn -Value $AtLogOn -IsExist ($PSBoundParameters.ContainsKey('AtLogOn'))

    # UserId (AtLogOn execute UserId)
    $returnHash.AtLogonUserId = TestScheduledTaskTriggerUserIdLogonTrigger -ScheduledTaskXml $xml -Parameter UserId -Value $AtLogOnUserId -IsExist ($PSBoundParameters.ContainsKey('AtLogOnUserId'));

    #endregion

    return $returnHash
}

#endregion

#region Remove Helper

function RemoveScheduledTaskEmptyDirectoryPath {
    # validate target Directory is existing
    $path = Join-Path $env:windir "System32\Tasks"
    $result = Get-ChildItem -Path $path -Directory | where Name -ne "Microsoft"
    if (($result | measure).count -eq 0) { return; }

    # validate Child is blank
    $result.FullName `
        | where {(Get-ChildItem -Path $_) -eq $null} `
        | Remove-Item -Force
}

#endregion

#region Extension Helper

function New-ZipPairs {
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $false, Position = 0, ValueFromPipeline = 1, ValueFromPipelineByPropertyName = 1)]
        [PSObject[]]$first,
 
        [parameter(Mandatory = $false, Position = 1, ValueFromPipelineByPropertyName = 1)]
        [PSObject[]]$second,

        [parameter(Mandatory = $false, Position = 2, ValueFromPipelineByPropertyName = 1)]
        [scriptBlock]$resultSelector
    )

    begin {
        $e2 = @($second).GetEnumerator()
        $psvariable = New-Object 'System.Collections.Generic.List[System.Management.Automation.psvariable]'
    }

    process {
        if ([string]::IsNullOrWhiteSpace($first)) { break }        
        if ([string]::IsNullOrWhiteSpace($second)) { break }
        
        try {
            $e1 = @($first).GetEnumerator()

            while ($e1.MoveNext() -and $e2.MoveNext()) {
                if ($PSBoundParameters.ContainsKey('resultSelector')) {
                    $first = $e1.Current
                    $second = $e2.Current
                    $context = $resultselector.InvokeWithContext(
                        $null,
                        ($psvariable),
                        {
                            (New-Object System.Management.Automation.PSVariable ("first", $first)),
                            (New-Object System.Management.Automation.PSVariable ("second", $second))
                        }
                    )
                    $context
                }
                else {
                    $tuple = New-Object 'System.Tuple[PSObject, PSObject]' ($e1.Current, $e2.current)
                    $tuple
                }
            }
        }
        finally {
            if (($d1 = $e1 -as [IDisposable]) -ne $null) { $d1.Dispose() }
            if (($d2 = $e2 -as [IDisposable]) -ne $null) { $d2.Dispose() }
            if (($d3 = $psvariable -as [IDisposable]) -ne $null) {$d3.Dispose() }
            if (($d4 = $context -as [IDisposable]) -ne $null) {$d4.Dispose() }
            if (($d5 = $tuple -as [IDisposable]) -ne $null) {$d5.Dispose() }
        }
    }
}

#endregion

Export-ModuleMember -Function *-TargetResource