PowerShellで重いタスクスケジューラーに頼らずcronを実行・管理する方法

いわゆるcron、Windowsで言う所のタスクスケジューラ。タスクスケジューラでいいじゃん？ってなるかもしれないけど、タスクスケジューラのUIくっそ重くね？新しくウィンドウを開く度に、トリガーの設定をちょっと弄くろうとする度にうんざりする。

ので作った。1つのps1ファイル。タスクごとにユニークなキー名を用意する。外部のjsonファイルにタスクのキー名をキー、タスクを実行した日時のunixTimeを値とするハッシュを記録する。
タスクごとに処理はご自由に。TimeSpan型を渡してタスクを実行するかどうか調べる。
俺の環境ではタスクスケジューラにこのスクリプトを登録して1分ごとに実行させている。もちろん実行するべきタスクが無い時はすぐに終わる

1分おきにPowerShellのシェルが表示されたらウザくて禿げるからこっちを参考にして完全バックグラウンドで実行させる。
[「windows」で「PowerShell」を「一切画面表示せず」に「タスクスケジューラに登録」する方法を再確認 - Qiita](http://qiita.com/Fushihara/items/1576f08825f39706174f)

以下コード。タスクの実行ログとか例外のログも出るよ

```ps1:cron.ps1
# utf16です
# タスクを最後に実行した日時を記録するjson
$script:runtimeFile="${PSScriptRoot}\cron-runtime.json"
# タスクを実行した時間、タスクの完了までにかかった時間を記録するテキストファイル
$script:executeTimeFile="${PSScriptRoot}\cron-execute.txt"
# タスクの実行中に発生した例外を記録するテキストファイル
$script:exceptionFile="${PSScriptRoot}\cron-exception.txt"

$ErrorActionPreference = "Stop"
$script:hash=@{  }

if(Test-Path -path $script:runtimeFile){
	# タスクを最後に実行した日時を読み込む
	Get-Content $script:runtimeFile -Encoding UTF8 -Raw | ConvertFrom-Json|ForEach-Object {
		foreach ($property in ($_ | Get-Member -MemberType NoteProperty))
		{
			$value=$_ | Select-Object -ExpandProperty $property.Name
			$key=$property.Name
			$script:hash[$key]=$value
		}
	}
}

function timeCheck([string]$key,[timespan]$timeSpan){
	# 指定の名前のタスクを最後に実行した時刻を調べて、タスクを実行するべきかどうか返す
	if ( $script:hash.ContainsKey($key.Trim()) ) {
		$beforeUnixTime=getDateFromUnixTimeString(($script:hash)[$key.Trim()])
		if( (get-date) -gt ($beforeUnixTime + $timeSpan) ){
			$true
		}else{
			$false
		}
	}else{
		$true
	}
}
function getUnixTimeStringFromDate($dateObject){
	# 引数の日付オブジェクトからunixTimeを取得する。秒単位。doubleなので小数点アリ
	$unixBaseTime = Get-Date -Date "1970/01/01"
	(New-TimeSpan -Start $unixBaseTime -End $dateObject).TotalSeconds
}
function getDateFromUnixTimeString($unixTimeString){
	# 引数の文字列をdoubleの秒単位のunixTimeとしてDateTimeオブジェクトを返す。エラー時は1970/1/1を返す
	try{
		$unixTimeDouble = [convert]::ToDouble($unixTimeString)
		$unixBaseTime = Get-Date -Date "1970/01/01"
		$unixBaseTime + (New-TimeSpan -Seconds $unixTimeDouble)
	}catch{
		Get-Date -Date "1970/01/01"
	}
}
function doEvent([string]$hashkey,[TimeSpan]$timeSpan,$callback){
	# タスクの実行を要請する。タスクの実行間隔を調べて指定未満だったら実行しない
	$start = get-date
	$error = ""
	if( ( timeCheck $hashkey $timeSpan ) -eq $false ){
		return
	}
	try{
		& $callBack
	}catch{
		$error = $_
	}
	$end = get-date
	($script:hash)[$hashkey] = getUnixTimeStringFromDate($end)
	# ログ書き出し
	$logText =$start.toString("yyyy/MM/dd(ddd)HH:mm:ss")+" "+($end-$start).TotalSeconds.toString("00.00")+"sec "
	if( $error -ne "" ){
		$logText+="★ "
	}
	$logText+=$hashkey
	$logText >> $script:executeTimeFile
	if ( $error -ne "" ){
		$start.toString("yyyy/MM/dd(ddd)HH:mm:ss")+" "+$hashkey >> $script:exceptionFile
		$error >> $script:exceptionFile
		"" >> $script:exceptionFile
		"" >> $script:exceptionFile
	}
}

# cronここから----------------------------------------------------
# New-TimeSpan -Seconds 10 -Days -Hours -Minutes

doEvent "ntp" (New-TimeSpan -Days 1) {
	$logPath="${PSScriptRoot}\ntp.log"
	w32tm /stripchart /computer:ntp.jst.mfeed.ad.jp /samples:2 >> $logPath
	w32tm /resync >> $logPath
}
doEvent "backup" (New-TimeSpan -Hours 1) {
}
ConvertTo-Json -InputObject $script:hash  > $script:runtimeFile
```