<#

.SYNOPSIS

Create the task for automatic updating of the GeoIP database

.DESCRIPTION

This PowerShell script creates (or removes) the Windows task that
automatically updates MaxMind's GeoIP database every second Wednesday
of the month. If the task with the specified name and in the specified
folder already exists, it's properties will be modified as specified
by the other options.

.PARAMETER taskName

Name of the updating task (default: "GeoIPUpdate").

.PARAMETER taskDescription

Description of the task (default: "GeoIP database updater")

.PARAMETER taskPath

Path of the updater task (default: "\")

.PARAMETER runTime

Time of the day when the task should be run (default: "00:00:00").

.PARAMETER geoipupdate

Path to the program "geoipupdate.exe" (default: "geoipupdate.exe")

.PARAMETER geoipconfig

Path to the config for for the program "geoipupdate.exe" (default: "geoip.cfg")

.PARAMETER remove

Delete the task with the specified name instead of creating it.

.PARAMETER silent

Do not display any information messages.

.PARAMETER help

Show short usage help and exit.

.EXAMPLE

.\geoipupdtask.ps1

Creates a task named "GeoIPUpdate" that runs at 00:00 every second
Wednesday of the month and updates the GeoIP database.

.EXAMPLE

.\geoipupdtask.ps1 -taskname "GeoIP Updater" -runtime 03:15

Creates a task named "GeoIP Updater" that runs at 03:15:00 every second
Wednesday of the month and updates the GeoIP database.

.EXAMPLE

.\geoipupdtask.ps1 -remove

Removes the task named "GeoIPUpdate".

#>

[CmdletBinding()]
Param (
    [string] $taskName = "GeoIPUpdate",	# Task name
    [string] $taskDescription = "GeoIP database updater",	# Task description
    [string] $taskPath = "\",	# Task path
    [string] $runTime = "00:00:00",	# Time of the day at which the task will run
    [string] $geoipupdate = "geoipupdate.exe",	# Path to the program geoipupdate.exe
    [string] $geoipconfig = "geoip.cfg",	# Path to the config file of the program geoipupdate.exe
    [switch] $remove,	# Remove the task instead of creating it
    [switch] $silent,	# If specified, the information messages are suppressed
    [switch] $help	# Show usage help and exit
)

$ErrorActionPreference = "Stop"

function removeTask {
    Param (
        [string] $taskName = "GeoIPUpdate",
        [string] $taskPath = "\",
        [bool] $silent = $false
    )
    $task = Get-ScheduledTask -TaskName $taskName -TaskPath $taskPath -ErrorAction SilentlyContinue
    if (-not $task) {
        Write-Host "The task '$taskName' does not exist." -ForegroundColor Red
        return 1
    }
    if (-not $silent) {
        Write-Output "Removing the task named '$taskPath$taskName'."
    }
    Unregister-ScheduledTask -TaskPath $taskPath -TaskName $taskName -Confirm: $false
    return 0
}

Function ConvertTo-BitFlag {
    [OutputType([int])]
    param (
        [Parameter(Mandatory, ValueFromPipeline)]
        [int[]]
        $InputObject
    )

    begin {
        $val = 0
    }
    process {
        foreach ($i in $InputObject) {
            $val = $val -bor (1 -shl ($i - 1))
        }
    }
    end {
        $val
    }
}

function createTask {
    Param (
        [string] $taskName = "GeoIPUpdate",
        [string] $taskDescription = "GeoIP database updater",
        [string] $taskPath = "\",
        [string] $runTime = "00:00:00",
        [string] $geoipupdate,
        [string] $geoipconfig,
        [bool] $silent = $false
    )
    try {
        $parsedTime = [TimeSpan]::Parse($runTime)
    }
    catch {
        Write-Host "'$runTime' is not a valid time." -ForegroundColor Red
        return 1
    }

    if (-not $taskPath.StartsWith("\")) {
        $taskPath = "\" + $taskPath
    }
    if (-not $taskPath.EndsWith("\")) {
        $taskPath = $taskPath + "\"
    }

    $task = Get-ScheduledTask -TaskName $taskName -TaskPath $taskPath -ErrorAction SilentlyContinue

    if (-not $silent) {
        if ($task) {
            Write-Host "The task '$taskPath$taskName' already exists."
            Write-Host "Its properties will be modified accordingly."
            Write-Host "Task description set to '$taskDescription'."
        }
        else {
            Write-Host "Creating task named '$taskPath$taskName', described as '$taskDescription'."
        }
        Write-Host "The task will be run at $parsedTime every 2nd Wednesday of the month."
        Write-Host "It will execute the program '`"$geoipupdate`"' with the arguments '-f `"$geoipconfig`"'."
    }

    $TASK_ACTION_EXEC = 0
    $TASK_LOGON_S4U = 2
    $TASK_TRIGGER_MONTHLYDOW = 5
    $TASK_CREATE_OR_UPDATE = 6
    $TASK_COMPATIBILITY_V2_4 = 6

    $start = (Get-Date).ToString("yyyy-MM-dd") + "T" + $runTime
    $daysOfWeek = @([DayOfWeek]::Wednesday)
    $weeksOfMonth = @(2)
    $monthsOfYear = @(1..12)

    $service = New-Object -ComObject Schedule.Service
    $service.Connect()
    
    try {
        $folder = $service.GetFolder($taskPath)
    }
    catch {
        if (-not $silent) {
            Write-Host "Task folder '$taskPath' does not exist. Creating it."
        }
        $folderName = $taskPath.Trim('\')
        
        if ($folderName -eq "") {
            $folder = $service.GetFolder("\")
        }
        else {
            $pathParts = $folderName -split '\\'
            $currentFolder = $service.GetFolder("\")
            
            foreach ($part in $pathParts) {
                if ($part -ne "") {
                    try {
                        $currentFolder = $currentFolder.GetFolder($part)
                    }
                    catch {
                        $currentFolder = $currentFolder.CreateFolder($part)
                    }
                }
            }
            
            $folder = $currentFolder
        }
    }

    $task = $service.NewTask(0)
    $task.RegistrationInfo.Description = $taskDescription
    $action = $task.Actions.Create($TASK_ACTION_EXEC)
    $trigger = $task.Triggers.Create($TASK_TRIGGER_MONTHLYDOW)
    $settings = $task.Settings

    $action.Path = "`"$geoipupdate`""
    $action.Arguments = "-f `"$geoipconfig`""

    $trigger.StartBoundary = $start
    $trigger.DaysOfWeek = $daysOfWeek | ForEach-Object { $_ + 1 } | ConvertTo-BitFlag
    $trigger.WeeksOfMonth = $weeksOfMonth | ConvertTo-BitFlag
    $trigger.MonthsOfYear = $monthsOfYear | ConvertTo-BitFlag

    $settings.Compatibility = $TASK_COMPATIBILITY_V2_4
    $settings.StartWhenAvailable = $true
    $settings.Hidden = $true
    $settings.RunOnlyIfNetworkAvailable = $true
    $settings.DisallowStartIfOnBatteries = $false

    $null = $folder.RegisterTaskDefinition($taskName, $task, $TASK_CREATE_OR_UPDATE, $null, $null, $TASK_LOGON_S4U)

    return 0
}

if ($help) {
    $scriptName = $MyInvocation.MyCommand.Name
    Write-Host "Usage: $scriptName [[-taskname] TaskName] [[-taskdescription] TaskDescription] [[-taskpath] TaskPath]"
    Write-Host "`t`t`t[[-geoipupdate] GeoIPUpdateProg] [[-geoipconfig] GeoIPUpdateConf]"
    Write-Host "`t`t`t[[-runtime] RunTime] [-remove] [-silent] [-help]"
    Write-Host "`tTaskName`tTask name (default: 'GeoIPUpdate')"
    Write-Host "`tTaskDescription`tTask description (default: 'GeoIP database updater')"
    Write-Host "`tTaskPath`tTask path; specify both leading and trailing '\' (default: '\')"
    Write-Host "`tRunTime`t`tTime of the day at which the task should run (default: '00:00:00')"
    Write-Host "`tGeoIPUpdateProg`tPath to the program 'geoipupdate.exe' (default: 'geoipupdate.exe')"
    Write-Host "`tGeoIPUpdateConf`tPath to the config file of the program 'geoipupdate.exe' (default: 'geoip.cfg')"
    Write-Host "`tremove`t`tRemove the task (if it exists) instead of creating it"
    Write-Host "`tsilent`t`tSuppress information messages"
    Write-Host "`thelp`t`tShow this help and exit"
    Exit
}

if (-not(Test-Path -path $geoipconfig)) {
    Write-Host "Cannot find the file '$geoipconfig'." -ForegroundColor Red
    Exit 1
}

if ($null -eq (Get-Command $geoipupdate -ErrorAction SilentlyContinue)) {
    Write-Host "Cannot find the program '$geoipupdate'." -ForegroundColor Red
    Exit 1
}

if ($remove) {
    $errorcode = removeTask $taskName $taskPath $silent
} else {
    $errorcode = createTask $taskName $taskDescription $taskPath $runTime $geoipupdate $geoipconfig $silent
}

Exit $errorcode
