MENU

outlookでメールを送信するプログラムと、登録したプログラムをスケジュール実行するプログラム

目次

outlookでメールを送信するプログラム

send_now.vbs

' send_now.vbs ── クリックで即送信(宛先固定)
Option Explicit
On Error Resume Next

Dim ol, mi
Set ol = CreateObject("Outlook.Application")
If Err.Number <> 0 Then
  MsgBox "Outlook を起動できませんでした。Outlook がインストールされているか確認してください。", 16, "送信エラー"
  WScript.Quit 1
End If
On Error GoTo 0

Set mi = ol.CreateItem(0) ' 0 = MailItem
' ====== 固定する宛先・内容 ======
mi.To      = "◯◯◯@gmail.com;◯◯◯@gmail.com"
mi.CC      = ""  ' 必要なら
mi.BCC     = ""  ' 必要なら
mi.Subject = "【テスト】クリック即送信"
mi.Body = "このメールはスクリプトから即送信されました。" & vbCrLf & vbCrLf & _
          "2行目のテキストです。" & vbCrLf & _
          "3行目のテキストです。"
' 添付が必要なら次を有効化:
mi.Attachments.Add "C:\Users\◯◯◯\◯◯◯\◯◯◯\◯◯◯\◯◯◯.pdf"
' ==============================

mi.Send    ' ← 即送信(送信トレイに格納→既定の送受信で出ていきます)
' 完了通知が要らなければ次のメッセージは削ってOK
MsgBox "送信トレイへ投入しました。", 64, "送信完了"

登録したプログラムをスケジュール実行するプログラム

Run_MiniScheduler.vbs

' Run_MiniScheduler.vbs - PowerShellを完全非表示で起動
Dim sh, script
Set sh = CreateObject("WScript.Shell")
script = """" & Replace(WScript.ScriptFullName, "Run_MiniScheduler.vbs", "MiniScheduler3.ps1") & """"
sh.Run "powershell.exe -NoProfile -ExecutionPolicy Bypass -STA -WindowStyle Hidden -File " & script, 0, False

MiniScheduler3.ps1

最新版はこちら

あわせて読みたい
プログラムをスケジュール実行するミニスケジューラ(最新版) # MiniScheduler3.ps1 - 8枠版(日本語UI / 繰り返し / 全自動設定 / 週末→月曜シフト / 実行後に最小化) # ・既定の関連付けで開く(ダブルクリック相当) # ・最小化...
# MiniScheduler3.ps1 - 8枠版(日本語UI / 繰り返し / 全自動設定 / 週末→月曜シフト / 実行後に最小化)
# ・既定の関連付けで開く(ダブルクリック相当)
# ・最小化中のみタスクトレイ表示、×で終了
# ・RunAt は ISO8601 で保存、読込は厳密復元(ParseExact "o")
# ・繰り返し: なし/毎日/毎週/毎月1日/毎月指定日
# ・[自動設定] と [全てを自動設定] は “今より未来” に合わせ、土日なら翌月曜へ
# ・表示は日本語、内部コードは英語(None/Daily/Weekly/Monthly1st/MonthlyDay)を保持
# ・手動実行・自動実行のいずれも、実行後に最小化してトレイへ格納

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
[System.Windows.Forms.Application]::EnableVisualStyles()

# ===== 設定 =====
$TrayOnlyWhenMinimized = $true
$JobCount = 8  # ← 枠数

# ===== パス・ログ =====
$AppDir   = Split-Path -Parent $MyInvocation.MyCommand.Path
$JobsPath = Join-Path $AppDir "jobs3.json"
$LogPath  = Join-Path $AppDir "MiniScheduler3.log"
function Write-Log([string]$msg){ try{ Add-Content -Path $LogPath -Value ("{0} {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"),$msg) -Encoding UTF8 }catch{} }

# ===== 表示は日本語・内部コードは英語 =====
$RecurrenceItems = @(
    @{ Text = "なし";       Value = "None" }
    @{ Text = "毎日";       Value = "Daily" }
    @{ Text = "毎週";       Value = "Weekly" }
    @{ Text = "毎月1日";    Value = "Monthly1st" }
    @{ Text = "毎月指定日"; Value = "MonthlyDay" }
)
function Init-RecurrenceCombo([System.Windows.Forms.ComboBox]$cb){
    $cb.DropDownStyle = "DropDownList"
    $cb.DisplayMember = "Text"
    $cb.ValueMember   = "Value"
    foreach ($it in $RecurrenceItems){ [void]$cb.Items.Add( (New-Object PSObject -Property $it) ) }
}
function Set-ComboByValue([System.Windows.Forms.ComboBox]$cb,[string]$val){
    foreach($item in $cb.Items){ if($item.Value -eq $val){ $cb.SelectedItem=$item; return } }
    foreach($item in $cb.Items){ if($item.Value -eq "None"){ $cb.SelectedItem=$item; break } }
}
function Get-ComboValue([System.Windows.Forms.ComboBox]$cb){
    if($cb.SelectedItem -and $cb.SelectedItem.PSObject.Properties.Match('Value').Count -gt 0){ return [string]$cb.SelectedItem.Value }
    return "None"
}

# ===== タスクトレイ =====
$script:tray = New-Object System.Windows.Forms.NotifyIcon
$script:tray.Icon = [System.Drawing.SystemIcons]::Application
$script:tray.Text = "ミニスケジューラー"
$script:tray.Visible = (-not $TrayOnlyWhenMinimized)
$cms = New-Object System.Windows.Forms.ContextMenuStrip
$miOpen = $cms.Items.Add("開く")
$miExit = $cms.Items.Add("終了")
$script:tray.ContextMenuStrip = $cms

# ===== モデル =====
function New-EmptyJob{
  [ordered]@{ Enabled=$false; Path=""; RunAt=(Get-Date); Recurrence="None"; DayOfMonth=1; LastRun=""; Status="" }
}
$Jobs = @()
1..$JobCount | ForEach-Object { $Jobs += New-EmptyJob }

# --- 読込(RunAtはISO8601) ---
if(Test-Path $JobsPath){
  try{
    $loaded = Get-Content $JobsPath -Raw | ConvertFrom-Json
    for($i=0;$i -lt [Math]::Min($JobCount,$loaded.Count);$i++){
      $Jobs[$i] = $loaded[$i]
      if($Jobs[$i].RunAt){
        try{
          if($Jobs[$i].RunAt -is [string]){ $Jobs[$i].RunAt = [datetime]::ParseExact($Jobs[$i].RunAt,"o",$null) }
        }catch{ $Jobs[$i].RunAt = Get-Date }
      } else { $Jobs[$i].RunAt = Get-Date }
      if(-not $Jobs[$i].Recurrence){ $Jobs[$i].Recurrence="None" }
      if(-not $Jobs[$i].DayOfMonth){ $Jobs[$i].DayOfMonth=1 }
    }
  }catch{ Write-Log "設定読込エラー: $($_.Exception.Message)" }
}
# --- 保存(RunAtはISO8601) ---
function Save-Jobs{
  $arr = for($i=0;$i -lt $JobCount;$i++){
    $j=$Jobs[$i]
    New-Object psobject -Property ([ordered]@{
      Enabled=[bool]$j.Enabled; Path=[string]$j.Path
      RunAt=([datetime]$j.RunAt).ToString("o")
      Recurrence=[string]$j.Recurrence; DayOfMonth=[int]$j.DayOfMonth
      LastRun=[string]$j.LastRun; Status=[string]$j.Status
    })
  }
  try{ ($arr|ConvertTo-Json -Depth 6) | Set-Content -Path $JobsPath -Encoding UTF8 }catch{ Write-Log "設定保存エラー: $($_.Exception.Message)" }
}

# ===== 日付計算(週末→月曜シフト対応) =====
function Get-EndOfMonth([datetime]$d){ (Get-Date -Year $d.Year -Month $d.Month -Day 1).AddMonths(1).AddDays(-1) }
function Shift-WeekendToMonday([datetime]$d){
  switch([int]$d.DayOfWeek){
    6 { return $d.AddDays(2) }  # Sat -> Mon
    0 { return $d.AddDays(1) }  # Sun -> Mon
    default { return $d }
  }
}
function Get-NextOccurrence([string]$rule,[datetime]$runAt,[int]$day){
  $now = Get-Date
  $t = Get-Date -Hour $runAt.Hour -Minute $runAt.Minute -Second $runAt.Second -Millisecond 0
  switch($rule){
    "Daily" {
      $c = Get-Date -Year $now.Year -Month $now.Month -Day $now.Day -Hour $t.Hour -Minute $t.Minute -Second $t.Second
      if($c -le $now){ $c = $c.AddDays(1) }
      return (Shift-WeekendToMonday $c)
    }
    "Weekly" {
      $target = [int]$runAt.DayOfWeek
      $c = Get-Date -Year $now.Year -Month $now.Month -Day $now.Day -Hour $t.Hour -Minute $t.Minute -Second $t.Second
      while(([int]$c.DayOfWeek -ne $target) -or ($c -le $now)){ $c=$c.AddDays(1) }
      return (Shift-WeekendToMonday $c)
    }
    "Monthly1st" {
      $base = Get-Date -Year $now.Year -Month $now.Month -Day 1 -Hour $t.Hour -Minute $t.Minute -Second $t.Second
      $c = if($base -le $now){ $base.AddMonths(1) } else { $base }
      return (Shift-WeekendToMonday $c)
    }
    "MonthlyDay" {
      $d = [Math]::Min($day,(Get-EndOfMonth $now).Day)
      $base = Get-Date -Year $now.Year -Month $now.Month -Day $d -Hour $t.Hour -Minute $t.Minute -Second $t.Second
      if($base -le $now){
        $m = $now.AddMonths(1)
        $d2 = [Math]::Min($day,(Get-EndOfMonth $m).Day)
        $base = Get-Date -Year $m.Year -Month $m.Month -Day $d2 -Hour $t.Hour -Minute $t.Minute -Second $t.Second
      }
      return (Shift-WeekendToMonday $base)
    }
    default {  # None
      $c = if($runAt -le $now){ $now.AddMinutes(1) } else { $runAt }
      return (Shift-WeekendToMonday $c)
    }
  }
}

# ===== UI(DPIレイアウト)=====
$form = New-Object System.Windows.Forms.Form
$form.Text="ミニスケジューラー(8枠)"
$form.StartPosition="CenterScreen"
$form.MinimumSize=New-Object System.Drawing.Size(1100,680)
$form.AutoScaleMode=[System.Windows.Forms.AutoScaleMode]::Dpi

$tbl = New-Object System.Windows.Forms.TableLayoutPanel
$tbl.Dock="Fill"; $tbl.ColumnCount=9
$tbl.Padding=New-Object System.Windows.Forms.Padding(8)
$tbl.AutoSize=$true; $tbl.AutoScroll=$true
$tbl.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::AutoSize)))
$tbl.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent,100)))
$tbl.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::AutoSize)))
$tbl.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::AutoSize)))
$tbl.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::AutoSize)))
$tbl.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::AutoSize)))
$tbl.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::AutoSize)))
$tbl.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::AutoSize)))
$tbl.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::AutoSize)))

$txtPath=@(); $btnBrowse=@(); $chkEnable=@(); $dtRun=@(); $btnRunNow=@(); $lblStatus=@(); $cbRule=@(); $nudDay=@(); $btnAuto=@()

for($i=0;$i -lt $JobCount;$i++){
  $row = $i*3
  $tbl.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::AutoSize)))
  $tbl.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::AutoSize)))
  $tbl.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::AutoSize)))

  $lbl=New-Object System.Windows.Forms.Label; $lbl.Text="プログラム{0}:" -f ($i+1); $lbl.AutoSize=$true
  $tbl.Controls.Add($lbl,0,$row)

  $t=New-Object System.Windows.Forms.TextBox; $t.Anchor="Left,Right"; $t.Width=620
  $tbl.Controls.Add($t,1,$row); $txtPath+=$t

  $b=New-Object System.Windows.Forms.Button; $b.Text="参照..."
  $tbl.Controls.Add($b,2,$row); $btnBrowse+=$b

  $c=New-Object System.Windows.Forms.CheckBox; $c.Text="有効"
  $tbl.Controls.Add($c,3,$row); $chkEnable+=$c

  $bn=New-Object System.Windows.Forms.Button; $bn.Text="今すぐ実行"
  $tbl.Controls.Add($bn,4,$row); $btnRunNow+=$bn

  $st=New-Object System.Windows.Forms.Label; $st.AutoSize=$true
  $tbl.Controls.Add($st,5,$row); $lblStatus+=$st

  $l2=New-Object System.Windows.Forms.Label; $l2.Text="実行日時:"; $l2.AutoSize=$true
  $tbl.Controls.Add($l2,0,$row+1)

  $dt=New-Object System.Windows.Forms.DateTimePicker; $dt.Format="Custom"; $dt.CustomFormat="yyyy/MM/dd HH:mm:ss"; $dt.Width=200
  $tbl.SetColumnSpan($dt,2); $tbl.Controls.Add($dt,1,$row+1); $dtRun+=$dt

  $btnA=New-Object System.Windows.Forms.Button; $btnA.Text="自動設定"
  $tbl.Controls.Add($btnA,4,$row+1); $btnAuto+=$btnA

  $l3=New-Object System.Windows.Forms.Label; $l3.Text="くり返し:"; $l3.AutoSize=$true
  $tbl.Controls.Add($l3,0,$row+2)

  $cb=New-Object System.Windows.Forms.ComboBox
  Init-RecurrenceCombo $cb
  $tbl.Controls.Add($cb,1,$row+2); $cbRule+=$cb

  $l4=New-Object System.Windows.Forms.Label; $l4.Text="日:"; $l4.AutoSize=$true
  $tbl.Controls.Add($l4,2,$row+2)

  $nud=New-Object System.Windows.Forms.NumericUpDown; $nud.Minimum=1; $nud.Maximum=31; $nud.Width=60
  $tbl.Controls.Add($nud,3,$row+2); $nudDay+=$nud
}

# 下部ボタン(行位置は可変: $footerRow)
$footerRow = $JobCount * 3
$tbl.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::AutoSize)))
$btnAutoAll = New-Object System.Windows.Forms.Button; $btnAutoAll.Text="全てを自動設定"
$btnSave = New-Object System.Windows.Forms.Button; $btnSave.Text="保存"
$btnExit = New-Object System.Windows.Forms.Button; $btnExit.Text="終了"
$pnl=New-Object System.Windows.Forms.FlowLayoutPanel; $pnl.FlowDirection='LeftToRight'; $pnl.AutoSize=$true
$pnl.Controls.Add($btnAutoAll); $pnl.Controls.Add($btnSave); $pnl.Controls.Add($btnExit)
$tbl.Controls.Add($pnl,0,$footerRow); $tbl.SetColumnSpan($pnl,9)
$form.Controls.Add($tbl)

# ===== 共同関数:トレイ格納(最小化) =====
function Minimize-ToTray {
    $form.WindowState = 'Minimized'
    $form.ShowInTaskbar = $false
    $form.Hide()
    $script:tray.Visible = $true
}

# ===== トレイ動作/最小化 =====
$restore = {
  $form.Show(); $form.WindowState="Normal"; $form.ShowInTaskbar=$true
  if($TrayOnlyWhenMinimized){$script:tray.Visible=$false}
  $form.Activate()
}
$miOpen.add_Click($restore)
$script:tray.add_DoubleClick($restore)
$miExit.add_Click({ $script:tray.Visible=$false; $script:tray.Dispose(); $form.Close() })

$min = {
  if($form.WindowState -eq [System.Windows.Forms.FormWindowState]::Minimized){
    $form.ShowInTaskbar=$false; $form.Hide(); $script:tray.Visible=$true
    try{ $script:tray.ShowBalloonTip(800,"ミニスケジューラー","通知領域で待機中です。",[System.Windows.Forms.ToolTipIcon]::None) }catch{}
  } elseif($form.WindowState -eq [System.Windows.Forms.FormWindowState]::Normal){
    $form.ShowInTaskbar=$true; if($TrayOnlyWhenMinimized){$script:tray.Visible=$false}else{$script:tray.Visible=$true}
  }
}
$form.Add_Resize($min); $form.Add_SizeChanged($min); $form.Add_ClientSizeChanged($min)
$form.Add_FormClosing({ $script:tray.Visible=$false; $script:tray.Dispose() })

# ===== 既存値→UI =====
for($i=0;$i -lt $JobCount;$i++){
  $txtPath[$i].Text=[string]$Jobs[$i].Path
  $chkEnable[$i].Checked=[bool]$Jobs[$i].Enabled
  $dtRun[$i].Value=[datetime]$Jobs[$i].RunAt
  $lblStatus[$i].Text=[string]$Jobs[$i].Status
  Set-ComboByValue $cbRule[$i] ([string]$Jobs[$i].Recurrence)
  $nudDay[$i].Value=[int]$Jobs[$i].DayOfMonth
}

# ===== 共通関数:1枠を自動設定 =====
function Apply-Auto([int]$idx){
  $rule = Get-ComboValue $cbRule[$idx]
  $next = Get-NextOccurrence $rule $dtRun[$idx].Value ([int]$nudDay[$idx].Value)
  $dtRun[$idx].Value=$next
  $Jobs[$idx].RunAt=$next
  $Jobs[$idx].Recurrence=$rule
  $Jobs[$idx].DayOfMonth=[int]$nudDay[$idx].Value
  $Jobs[$idx].Enabled=$true; $chkEnable[$idx].Checked=$true
  $Jobs[$idx].Status="次回: "+$next.ToString("yyyy/MM/dd HH:mm:ss")
  $lblStatus[$idx].Text=$Jobs[$idx].Status
}

function Try-UnblockFile([string]$path) {
  try {
    if (Test-Path -LiteralPath $path) {
      Unblock-File -LiteralPath $path -ErrorAction Stop
    }
  } catch { } # 失敗しても無視(権限や既に解除済みなど)
}

# ===== 参照/今すぐ/自動設定/全自動 =====
for($i=0;$i -lt $JobCount;$i++){ $btnBrowse[$i].Tag=$i; $btnRunNow[$i].Tag=$i; $btnAuto[$i].Tag=$i }

foreach($b in $btnBrowse){
  $b.Add_Click({
    param($s,$e)
    $i=[int]$s.Tag
    $ofd=New-Object System.Windows.Forms.OpenFileDialog
    $ofd.Filter="すべてのファイル|*.*"
    if($ofd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK){ $txtPath[$i].Text=$ofd.FileName }
  })
}
function Invoke-Path([string]$p){
  try{
    Try-UnblockFile $p          # ← 起動前にブロック解除
    $psi = New-Object System.Diagnostics.ProcessStartInfo
    $psi.FileName = $p
    $psi.UseShellExecute = $true   # 既定の関連付けで開く(ダブルクリック相当)
    [System.Diagnostics.Process]::Start($psi) | Out-Null
    return $true,""
  } catch {
    return $false,$_.Exception.Message
  }
}

foreach($b in $btnRunNow){
  $b.Add_Click({
    param($s,$e)
    $i=[int]$s.Tag; $p=$txtPath[$i].Text
    if([string]::IsNullOrWhiteSpace($p)){ [System.Windows.Forms.MessageBox]::Show("パスを入力してください。")|Out-Null; return }
    $ok,$err=Invoke-Path $p
    $Jobs[$i].LastRun=(Get-Date).ToString("yyyy/MM/dd HH:mm:ss")
    if($ok){
      if($Jobs[$i].Recurrence -eq "None"){ $Jobs[$i].Status="実行完了"; $Jobs[$i].Enabled=$false; $chkEnable[$i].Checked=$false }
      else{
        $next = Get-NextOccurrence $Jobs[$i].Recurrence $Jobs[$i].RunAt ([int]$Jobs[$i].DayOfMonth)
        $Jobs[$i].RunAt=$next; $dtRun[$i].Value=$next
        $Jobs[$i].Status="実行完了 → 次回: "+$next.ToString("yyyy/MM/dd HH:mm:ss")
      }
    } else { [System.Windows.Forms.MessageBox]::Show("実行エラー:`n"+$err)|Out-Null; $Jobs[$i].Status="実行エラー" }
    Save-Jobs; $lblStatus[$i].Text=$Jobs[$i].Status

    # ← 実行後に最小化してトレイへ
    Minimize-ToTray
  })
}

foreach($b in $btnAuto){
  $b.Add_Click({
    param($s,$e)
    $i=[int]$s.Tag
    Apply-Auto $i
    Save-Jobs
    [System.Windows.Forms.MessageBox]::Show("プログラム{0} を {1} に設定しました。".Replace("{0}",($i+1)).Replace("{1}",$Jobs[$i].RunAt.ToString("yyyy/MM/dd HH:mm:ss"))) | Out-Null
  })
}

$btnAutoAll.Add_Click({
  for($i=0;$i -lt $JobCount;$i++){ Apply-Auto $i }
  Save-Jobs
  [System.Windows.Forms.MessageBox]::Show(("{0}件すべてのスケジュールを自動設定しました。(土日は月曜へシフト)" -f $JobCount))|Out-Null
})

# ===== 保存・終了 =====
$btnSave.Add_Click({
  for($i=0;$i -lt $JobCount;$i++){
    $Jobs[$i].Path=$txtPath[$i].Text
    $Jobs[$i].Enabled=$chkEnable[$i].Checked
    $Jobs[$i].RunAt=$dtRun[$i].Value
    $Jobs[$i].Recurrence = Get-ComboValue $cbRule[$i]
    $Jobs[$i].DayOfMonth=[int]$nudDay[$i].Value
  }
  Save-Jobs
  [System.Windows.Forms.MessageBox]::Show("保存しました。")|Out-Null
})
$btnExit.Add_Click({ $script:tray.Visible=$false; $script:tray.Dispose(); $form.Close() })

# ===== タイマー(1秒)=====
$timer=New-Object System.Windows.Forms.Timer; $timer.Interval=1000
$timer.Add_Tick({
  $now=Get-Date
  for($i=0;$i -lt $JobCount;$i++){
    if(-not $Jobs[$i].Enabled){ continue }
    if($now -ge [datetime]$Jobs[$i].RunAt){
      $p=[string]$Jobs[$i].Path
      if([string]::IsNullOrWhiteSpace($p)){
        $Jobs[$i].Status="パス未設定"; $Jobs[$i].Enabled=$false; $chkEnable[$i].Checked=$false; Save-Jobs; $lblStatus[$i].Text=$Jobs[$i].Status; continue
      }
      $ok,$err=Invoke-Path $p
      $Jobs[$i].LastRun=(Get-Date).ToString("yyyy/MM/dd HH:mm:ss")
      if($ok){
        if($Jobs[$i].Recurrence -eq "None"){
          $Jobs[$i].Status="実行完了"; $Jobs[$i].Enabled=$false; $chkEnable[$i].Checked=$false
        } else {
          $next = Get-NextOccurrence $Jobs[$i].Recurrence $Jobs[$i].RunAt ([int]$Jobs[$i].DayOfMonth)
          $Jobs[$i].RunAt=$next; $dtRun[$i].Value=$next
          $Jobs[$i].Status="実行完了 → 次回: "+$next.ToString("yyyy/MM/dd HH:mm:ss")
        }
      } else { $Jobs[$i].Status="実行エラー" }
      Save-Jobs; $lblStatus[$i].Text=$Jobs[$i].Status

      # ← 自動実行後も最小化してトレイへ
      Minimize-ToTray
    }
  }
})
$timer.Start()

# ===== 表示 =====
[System.Windows.Forms.Application]::Run($form)
# MiniScheduler3.ps1 - 8枠版(日本語UI / 繰り返し / 全自動設定 / 週末→月曜シフト)
# ・既定の関連付けで開く(ダブルクリック相当)
# ・最小化中のみタスクトレイ表示、×で終了
# ・RunAt は ISO8601 で保存、読込は厳密復元(ParseExact "o")
# ・繰り返し: なし/毎日/毎週/毎月1日/毎月指定日
# ・[自動設定] と [全てを自動設定] は “今より未来” に合わせ、土日なら翌月曜へ
# ・表示は日本語、内部コードは英語(None/Daily/Weekly/Monthly1st/MonthlyDay)を保持

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
[System.Windows.Forms.Application]::EnableVisualStyles()

# ===== 設定 =====
$TrayOnlyWhenMinimized = $true
$JobCount = 8  # ← ここで枠数を変更できます(8)

# ===== パス・ログ =====
$AppDir   = Split-Path -Parent $MyInvocation.MyCommand.Path
$JobsPath = Join-Path $AppDir "jobs3.json"
$LogPath  = Join-Path $AppDir "MiniScheduler3.log"
function Write-Log([string]$msg){ try{ Add-Content -Path $LogPath -Value ("{0} {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"),$msg) -Encoding UTF8 }catch{} }

# ===== 表示は日本語・内部コードは英語 =====
$RecurrenceItems = @(
    @{ Text = "なし";       Value = "None" }
    @{ Text = "毎日";       Value = "Daily" }
    @{ Text = "毎週";       Value = "Weekly" }
    @{ Text = "毎月1日";    Value = "Monthly1st" }
    @{ Text = "毎月指定日"; Value = "MonthlyDay" }
)
function Init-RecurrenceCombo([System.Windows.Forms.ComboBox]$cb){
    $cb.DropDownStyle = "DropDownList"
    $cb.DisplayMember = "Text"
    $cb.ValueMember   = "Value"
    foreach ($it in $RecurrenceItems){ [void]$cb.Items.Add( (New-Object PSObject -Property $it) ) }
}
function Set-ComboByValue([System.Windows.Forms.ComboBox]$cb,[string]$val){
    foreach($item in $cb.Items){ if($item.Value -eq $val){ $cb.SelectedItem=$item; return } }
    foreach($item in $cb.Items){ if($item.Value -eq "None"){ $cb.SelectedItem=$item; break } }
}
function Get-ComboValue([System.Windows.Forms.ComboBox]$cb){
    if($cb.SelectedItem -and $cb.SelectedItem.PSObject.Properties.Match('Value').Count -gt 0){ return [string]$cb.SelectedItem.Value }
    return "None"
}

# ===== タスクトレイ =====
$script:tray = New-Object System.Windows.Forms.NotifyIcon
$script:tray.Icon = [System.Drawing.SystemIcons]::Application
$script:tray.Text = "ミニスケジューラー"
$script:tray.Visible = (-not $TrayOnlyWhenMinimized)
$cms = New-Object System.Windows.Forms.ContextMenuStrip
$miOpen = $cms.Items.Add("開く")
$miExit = $cms.Items.Add("終了")
$script:tray.ContextMenuStrip = $cms

# ===== モデル =====
function New-EmptyJob{
  [ordered]@{ Enabled=$false; Path=""; RunAt=(Get-Date); Recurrence="None"; DayOfMonth=1; LastRun=""; Status="" }
}
$Jobs = @()
1..$JobCount | ForEach-Object { $Jobs += New-EmptyJob }

# --- 読込(RunAtはISO8601) ---
if(Test-Path $JobsPath){
  try{
    $loaded = Get-Content $JobsPath -Raw | ConvertFrom-Json
    for($i=0;$i -lt [Math]::Min($JobCount,$loaded.Count);$i++){
      $Jobs[$i] = $loaded[$i]
      if($Jobs[$i].RunAt){
        try{
          if($Jobs[$i].RunAt -is [string]){ $Jobs[$i].RunAt = [datetime]::ParseExact($Jobs[$i].RunAt,"o",$null) }
        }catch{ $Jobs[$i].RunAt = Get-Date }
      } else { $Jobs[$i].RunAt = Get-Date }
      if(-not $Jobs[$i].Recurrence){ $Jobs[$i].Recurrence="None" }
      if(-not $Jobs[$i].DayOfMonth){ $Jobs[$i].DayOfMonth=1 }
    }
  }catch{ Write-Log "設定読込エラー: $($_.Exception.Message)" }
}
# --- 保存(RunAtはISO8601) ---
function Save-Jobs{
  $arr = for($i=0;$i -lt $JobCount;$i++){
    $j=$Jobs[$i]
    New-Object psobject -Property ([ordered]@{
      Enabled=[bool]$j.Enabled; Path=[string]$j.Path
      RunAt=([datetime]$j.RunAt).ToString("o")
      Recurrence=[string]$j.Recurrence; DayOfMonth=[int]$j.DayOfMonth
      LastRun=[string]$j.LastRun; Status=[string]$j.Status
    })
  }
  try{ ($arr|ConvertTo-Json -Depth 6) | Set-Content -Path $JobsPath -Encoding UTF8 }catch{ Write-Log "設定保存エラー: $($_.Exception.Message)" }
}

# ===== 日付計算(週末→月曜シフト対応) =====
function Get-EndOfMonth([datetime]$d){ (Get-Date -Year $d.Year -Month $d.Month -Day 1).AddMonths(1).AddDays(-1) }
function Shift-WeekendToMonday([datetime]$d){
  switch([int]$d.DayOfWeek){
    6 { return $d.AddDays(2) }  # Sat -> Mon
    0 { return $d.AddDays(1) }  # Sun -> Mon
    default { return $d }
  }
}
function Get-NextOccurrence([string]$rule,[datetime]$runAt,[int]$day){
  $now = Get-Date
  $t = Get-Date -Hour $runAt.Hour -Minute $runAt.Minute -Second $runAt.Second -Millisecond 0
  switch($rule){
    "Daily" {
      $c = Get-Date -Year $now.Year -Month $now.Month -Day $now.Day -Hour $t.Hour -Minute $t.Minute -Second $t.Second
      if($c -le $now){ $c = $c.AddDays(1) }
      return (Shift-WeekendToMonday $c)
    }
    "Weekly" {
      $target = [int]$runAt.DayOfWeek
      $c = Get-Date -Year $now.Year -Month $now.Month -Day $now.Day -Hour $t.Hour -Minute $t.Minute -Second $t.Second
      while(([int]$c.DayOfWeek -ne $target) -or ($c -le $now)){ $c=$c.AddDays(1) }
      return (Shift-WeekendToMonday $c)
    }
    "Monthly1st" {
      $base = Get-Date -Year $now.Year -Month $now.Month -Day 1 -Hour $t.Hour -Minute $t.Minute -Second $t.Second
      $c = if($base -le $now){ $base.AddMonths(1) } else { $base }
      return (Shift-WeekendToMonday $c)
    }
    "MonthlyDay" {
      $d = [Math]::Min($day,(Get-EndOfMonth $now).Day)
      $base = Get-Date -Year $now.Year -Month $now.Month -Day $d -Hour $t.Hour -Minute $t.Minute -Second $t.Second
      if($base -le $now){
        $m = $now.AddMonths(1)
        $d2 = [Math]::Min($day,(Get-EndOfMonth $m).Day)
        $base = Get-Date -Year $m.Year -Month $m.Month -Day $d2 -Hour $t.Hour -Minute $t.Minute -Second $t.Second
      }
      return (Shift-WeekendToMonday $base)
    }
    default {  # None
      $c = if($runAt -le $now){ $now.AddMinutes(1) } else { $runAt }
      return (Shift-WeekendToMonday $c)
    }
  }
}

# ===== UI(DPIレイアウト)=====
$form = New-Object System.Windows.Forms.Form
$form.Text="ミニスケジューラー(8枠)"
$form.StartPosition="CenterScreen"
$form.MinimumSize=New-Object System.Drawing.Size(1100,680)
$form.AutoScaleMode=[System.Windows.Forms.AutoScaleMode]::Dpi

$tbl = New-Object System.Windows.Forms.TableLayoutPanel
$tbl.Dock="Fill"; $tbl.ColumnCount=9
$tbl.Padding=New-Object System.Windows.Forms.Padding(8)
$tbl.AutoSize=$true; $tbl.AutoScroll=$true
$tbl.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::AutoSize)))
$tbl.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent,100)))
$tbl.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::AutoSize)))
$tbl.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::AutoSize)))
$tbl.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::AutoSize)))
$tbl.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::AutoSize)))
$tbl.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::AutoSize)))
$tbl.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::AutoSize)))
$tbl.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::AutoSize)))

$txtPath=@(); $btnBrowse=@(); $chkEnable=@(); $dtRun=@(); $btnRunNow=@(); $lblStatus=@(); $cbRule=@(); $nudDay=@(); $btnAuto=@()

for($i=0;$i -lt $JobCount;$i++){
  $row = $i*3
  $tbl.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::AutoSize)))
  $tbl.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::AutoSize)))
  $tbl.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::AutoSize)))

  $lbl=New-Object System.Windows.Forms.Label; $lbl.Text="プログラム{0}:" -f ($i+1); $lbl.AutoSize=$true
  $tbl.Controls.Add($lbl,0,$row)

  $t=New-Object System.Windows.Forms.TextBox; $t.Anchor="Left,Right"; $t.Width=620
  $tbl.Controls.Add($t,1,$row); $txtPath+=$t

  $b=New-Object System.Windows.Forms.Button; $b.Text="参照..."
  $tbl.Controls.Add($b,2,$row); $btnBrowse+=$b

  $c=New-Object System.Windows.Forms.CheckBox; $c.Text="有効"
  $tbl.Controls.Add($c,3,$row); $chkEnable+=$c

  $bn=New-Object System.Windows.Forms.Button; $bn.Text="今すぐ実行"
  $tbl.Controls.Add($bn,4,$row); $btnRunNow+=$bn

  $st=New-Object System.Windows.Forms.Label; $st.AutoSize=$true
  $tbl.Controls.Add($st,5,$row); $lblStatus+=$st

  $l2=New-Object System.Windows.Forms.Label; $l2.Text="実行日時:"; $l2.AutoSize=$true
  $tbl.Controls.Add($l2,0,$row+1)

  $dt=New-Object System.Windows.Forms.DateTimePicker; $dt.Format="Custom"; $dt.CustomFormat="yyyy/MM/dd HH:mm:ss"; $dt.Width=200
  $tbl.SetColumnSpan($dt,2); $tbl.Controls.Add($dt,1,$row+1); $dtRun+=$dt

  $btnA=New-Object System.Windows.Forms.Button; $btnA.Text="自動設定"
  $tbl.Controls.Add($btnA,4,$row+1); $btnAuto+=$btnA

  $l3=New-Object System.Windows.Forms.Label; $l3.Text="くり返し:"; $l3.AutoSize=$true
  $tbl.Controls.Add($l3,0,$row+2)

  $cb=New-Object System.Windows.Forms.ComboBox
  Init-RecurrenceCombo $cb
  $tbl.Controls.Add($cb,1,$row+2); $cbRule+=$cb

  $l4=New-Object System.Windows.Forms.Label; $l4.Text="日:"; $l4.AutoSize=$true
  $tbl.Controls.Add($l4,2,$row+2)

  $nud=New-Object System.Windows.Forms.NumericUpDown; $nud.Minimum=1; $nud.Maximum=31; $nud.Width=60
  $tbl.Controls.Add($nud,3,$row+2); $nudDay+=$nud
}

# 下部ボタン(行位置は可変: $footerRow)
$footerRow = $JobCount * 3
$tbl.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::AutoSize)))
$btnAutoAll = New-Object System.Windows.Forms.Button; $btnAutoAll.Text="全てを自動設定"
$btnSave = New-Object System.Windows.Forms.Button; $btnSave.Text="保存"
$btnExit = New-Object System.Windows.Forms.Button; $btnExit.Text="終了"
$pnl=New-Object System.Windows.Forms.FlowLayoutPanel; $pnl.FlowDirection='LeftToRight'; $pnl.AutoSize=$true
$pnl.Controls.Add($btnAutoAll); $pnl.Controls.Add($btnSave); $pnl.Controls.Add($btnExit)
$tbl.Controls.Add($pnl,0,$footerRow); $tbl.SetColumnSpan($pnl,9)
$form.Controls.Add($tbl)

# ===== トレイ動作/最小化 =====
$restore = {
  $form.Show(); $form.WindowState="Normal"; $form.ShowInTaskbar=$true
  if($TrayOnlyWhenMinimized){$script:tray.Visible=$false}
  $form.Activate()
}
$miOpen.add_Click($restore)
$script:tray.add_DoubleClick($restore)
$miExit.add_Click({ $script:tray.Visible=$false; $script:tray.Dispose(); $form.Close() })

$min = {
  if($form.WindowState -eq [System.Windows.Forms.FormWindowState]::Minimized){
    $form.ShowInTaskbar=$false; $form.Hide(); $script:tray.Visible=$true
    try{ $script:tray.ShowBalloonTip(800,"ミニスケジューラー","通知領域で待機中です。",[System.Windows.Forms.ToolTipIcon]::None) }catch{}
  } elseif($form.WindowState -eq [System.Windows.Forms.FormWindowState]::Normal){
    $form.ShowInTaskbar=$true; if($TrayOnlyWhenMinimized){$script:tray.Visible=$false}else{$script:tray.Visible=$true}
  }
}
$form.Add_Resize($min); $form.Add_SizeChanged($min); $form.Add_ClientSizeChanged($min)
$form.Add_FormClosing({ $script:tray.Visible=$false; $script:tray.Dispose() })

# ===== 既存値→UI =====
for($i=0;$i -lt $JobCount;$i++){
  $txtPath[$i].Text=[string]$Jobs[$i].Path
  $chkEnable[$i].Checked=[bool]$Jobs[$i].Enabled
  $dtRun[$i].Value=[datetime]$Jobs[$i].RunAt
  $lblStatus[$i].Text=[string]$Jobs[$i].Status
  Set-ComboByValue $cbRule[$i] ([string]$Jobs[$i].Recurrence)
  $nudDay[$i].Value=[int]$Jobs[$i].DayOfMonth
}

# ===== 共通関数:1枠を自動設定 =====
function Apply-Auto([int]$idx){
  $rule = Get-ComboValue $cbRule[$idx]
  $next = Get-NextOccurrence $rule $dtRun[$idx].Value ([int]$nudDay[$idx].Value)
  $dtRun[$idx].Value=$next
  $Jobs[$idx].RunAt=$next
  $Jobs[$idx].Recurrence=$rule
  $Jobs[$idx].DayOfMonth=[int]$nudDay[$idx].Value
  $Jobs[$idx].Enabled=$true; $chkEnable[$idx].Checked=$true
  $Jobs[$idx].Status="次回: "+$next.ToString("yyyy/MM/dd HH:mm:ss")
  $lblStatus[$idx].Text=$Jobs[$idx].Status
}

# ===== 参照/今すぐ/自動設定/全自動 =====
for($i=0;$i -lt $JobCount;$i++){ $btnBrowse[$i].Tag=$i; $btnRunNow[$i].Tag=$i; $btnAuto[$i].Tag=$i }

foreach($b in $btnBrowse){
  $b.Add_Click({
    param($s,$e)
    $i=[int]$s.Tag
    $ofd=New-Object System.Windows.Forms.OpenFileDialog
    $ofd.Filter="すべてのファイル|*.*"
    if($ofd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK){ $txtPath[$i].Text=$ofd.FileName }
  })
}
function Invoke-Path([string]$p){ try{$psi=New-Object System.Diagnostics.ProcessStartInfo;$psi.FileName=$p;$psi.UseShellExecute=$true;[System.Diagnostics.Process]::Start($psi)|Out-Null;return $true,""}catch{return $false,$_.Exception.Message} }

foreach($b in $btnRunNow){
  $b.Add_Click({
    param($s,$e)
    $i=[int]$s.Tag; $p=$txtPath[$i].Text
    if([string]::IsNullOrWhiteSpace($p)){ [System.Windows.Forms.MessageBox]::Show("パスを入力してください。")|Out-Null; return }
    $ok,$err=Invoke-Path $p
    $Jobs[$i].LastRun=(Get-Date).ToString("yyyy/MM/dd HH:mm:ss")
    if($ok){
      if($Jobs[$i].Recurrence -eq "None"){ $Jobs[$i].Status="実行完了"; $Jobs[$i].Enabled=$false; $chkEnable[$i].Checked=$false }
      else{
        $next = Get-NextOccurrence $Jobs[$i].Recurrence $Jobs[$i].RunAt ([int]$Jobs[$i].DayOfMonth)
        $Jobs[$i].RunAt=$next; $dtRun[$i].Value=$next
        $Jobs[$i].Status="実行完了 → 次回: "+$next.ToString("yyyy/MM/dd HH:mm:ss")
      }
    } else { [System.Windows.Forms.MessageBox]::Show("実行エラー:`n"+$err)|Out-Null; $Jobs[$i].Status="実行エラー" }
    Save-Jobs; $lblStatus[$i].Text=$Jobs[$i].Status
  })
}

foreach($b in $btnAuto){
  $b.Add_Click({
    param($s,$e)
    $i=[int]$s.Tag
    Apply-Auto $i
    Save-Jobs
    [System.Windows.Forms.MessageBox]::Show("プログラム{0} を {1} に設定しました。".Replace("{0}",($i+1)).Replace("{1}",$Jobs[$i].RunAt.ToString("yyyy/MM/dd HH:mm:ss"))) | Out-Null
  })
}

$btnAutoAll.Add_Click({
  for($i=0;$i -lt $JobCount;$i++){ Apply-Auto $i }
  Save-Jobs
  [System.Windows.Forms.MessageBox]::Show(("{0}件すべてのスケジュールを自動設定しました。(土日は月曜へシフト)" -f $JobCount))|Out-Null
})

# ===== 保存・終了 =====
$btnSave.Add_Click({
  for($i=0;$i -lt $JobCount;$i++){
    $Jobs[$i].Path=$txtPath[$i].Text
    $Jobs[$i].Enabled=$chkEnable[$i].Checked
    $Jobs[$i].RunAt=$dtRun[$i].Value
    $Jobs[$i].Recurrence = Get-ComboValue $cbRule[$i]
    $Jobs[$i].DayOfMonth=[int]$nudDay[$i].Value
  }
  Save-Jobs
  [System.Windows.Forms.MessageBox]::Show("保存しました。")|Out-Null
})
$btnExit.Add_Click({ $script:tray.Visible=$false; $script:tray.Dispose(); $form.Close() })

# ===== タイマー(1秒)=====
$timer=New-Object System.Windows.Forms.Timer; $timer.Interval=1000
$timer.Add_Tick({
  $now=Get-Date
  for($i=0;$i -lt $JobCount;$i++){
    if(-not $Jobs[$i].Enabled){ continue }
    if($now -ge [datetime]$Jobs[$i].RunAt){
      $p=[string]$Jobs[$i].Path
      if([string]::IsNullOrWhiteSpace($p)){
        $Jobs[$i].Status="パス未設定"; $Jobs[$i].Enabled=$false; $chkEnable[$i].Checked=$false; Save-Jobs; $lblStatus[$i].Text=$Jobs[$i].Status; continue
      }
      $ok,$err=Invoke-Path $p
      $Jobs[$i].LastRun=(Get-Date).ToString("yyyy/MM/dd HH:mm:ss")
      if($ok){
        if($Jobs[$i].Recurrence -eq "None"){
          $Jobs[$i].Status="実行完了"; $Jobs[$i].Enabled=$false; $chkEnable[$i].Checked=$false
        } else {
          $next = Get-NextOccurrence $Jobs[$i].Recurrence $Jobs[$i].RunAt ([int]$Jobs[$i].DayOfMonth)
          $Jobs[$i].RunAt=$next; $dtRun[$i].Value=$next
          $Jobs[$i].Status="実行完了 → 次回: "+$next.ToString("yyyy/MM/dd HH:mm:ss")
        }
      } else { $Jobs[$i].Status="実行エラー" }
      Save-Jobs; $lblStatus[$i].Text=$Jobs[$i].Status
    }
  }
})
$timer.Start()

# ===== 表示 =====
[System.Windows.Forms.Application]::Run($form)
# MiniScheduler3.ps1 - 3枠のシンプル自動起動(関連付け起動・DPI対応・最小化時トレイ・×で終了)
# 修正: RunAt を ISO8601 で保存/厳密に復元。再起動時に現在時刻へリセットされない。
# 動作: Windows PowerShell 5.x / Windows 10+ / UTF-8

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
[System.Windows.Forms.Application]::EnableVisualStyles()

# ===== 設定 =====
$TrayOnlyWhenMinimized = $true   # 最小化中のみトレイに表示(常時にしたい場合は $false)

# ===== パス・ログ =====
$AppDir   = Split-Path -Parent $MyInvocation.MyCommand.Path
$JobsPath = Join-Path $AppDir "jobs3.json"
$LogPath  = Join-Path $AppDir "MiniScheduler3.log"

function Write-Log([string]$msg) {
  try { Add-Content -Path $LogPath -Value ("{0} {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $msg) -Encoding UTF8 } catch {}
}

Write-Log "=== App start ==="

# ===== タスクトレイ(先に作成し保持)=====
$script:tray = New-Object System.Windows.Forms.NotifyIcon
$script:tray.Icon = [System.Drawing.SystemIcons]::Application
$script:tray.Text = "ミニスケジューラー"
$script:tray.Visible = (-not $TrayOnlyWhenMinimized)

$cms = New-Object System.Windows.Forms.ContextMenuStrip
$miOpen = $cms.Items.Add("開く")
$miExit = $cms.Items.Add("終了")
$script:tray.ContextMenuStrip = $cms

# ===== モデル(3枠固定)=====
function New-EmptyJob { [ordered]@{ Enabled=$false; Path=""; RunAt=(Get-Date); LastRun=""; Status="" } }
$Jobs = @(
  New-EmptyJob
  New-EmptyJob
  New-EmptyJob
)

# --- JSON 読込(RunAt は ISO8601想定で厳密に datetime 化) ---
if (Test-Path $JobsPath) {
  try {
    $loaded = Get-Content $JobsPath -Raw | ConvertFrom-Json
    for ($i=0; $i -lt [Math]::Min(3, $loaded.Count); $i++) {
      $Jobs[$i] = $loaded[$i]
      if ($Jobs[$i].RunAt) {
        try {
          if ($Jobs[$i].RunAt -is [datetime]) {
            # そのまま
          } elseif ($Jobs[$i].RunAt -is [string]) {
            $Jobs[$i].RunAt = [datetime]::ParseExact($Jobs[$i].RunAt, "o", $null)
          } else {
            # 予期せぬ形は念のため通常の Parse
            $Jobs[$i].RunAt = [datetime]::Parse([string]$Jobs[$i].RunAt)
          }
        } catch {
          Write-Log "Parse RunAt failed -> fallback now: $($_.Exception.Message)"
          $Jobs[$i].RunAt = Get-Date
        }
      } else {
        $Jobs[$i].RunAt = Get-Date
      }
    }
  } catch { Write-Log ("設定読込エラー: " + $_.Exception.Message) }
}

# --- JSON 保存(RunAt は ISO8601 文字列で保存) ---
function Save-Jobs {
  $toSave = @()
  for ($i=0; $i -lt 3; $i++) {
    $j = $Jobs[$i]
    $obj = [ordered]@{
      Enabled = [bool]$j.Enabled
      Path    = [string]$j.Path
      RunAt   = ([datetime]$j.RunAt).ToString("o")  # ISO8601固定
      LastRun = [string]$j.LastRun
      Status  = [string]$j.Status
    }
    $toSave += (New-Object psobject -Property $obj)
  }
  try {
    ($toSave | ConvertTo-Json -Depth 6) | Set-Content -Path $JobsPath -Encoding UTF8
  } catch { Write-Log ("設定保存エラー: " + $_.Exception.Message) }
}

# ===== UI(DPI対応レイアウト)=====
$form = New-Object System.Windows.Forms.Form
$form.Text = "ミニスケジューラー(3枠)"
$form.StartPosition = "CenterScreen"
$form.MinimumSize = New-Object System.Drawing.Size(880, 360)
$form.AutoScaleMode = [System.Windows.Forms.AutoScaleMode]::Dpi

$tbl = New-Object System.Windows.Forms.TableLayoutPanel
$tbl.Dock = "Fill"
$tbl.ColumnCount = 6
$tbl.RowCount = 7
$tbl.Padding = New-Object System.Windows.Forms.Padding(8)
$tbl.AutoSize = $true
$tbl.AutoScroll = $true
$tbl.ColumnStyles.Add( (New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::AutoSize)) )
$tbl.ColumnStyles.Add( (New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 100)) )
$tbl.ColumnStyles.Add( (New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::AutoSize)) )
$tbl.ColumnStyles.Add( (New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::AutoSize)) )
$tbl.ColumnStyles.Add( (New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::AutoSize)) )
$tbl.ColumnStyles.Add( (New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::AutoSize)) )

$txtPath=@(); $btnBrowse=@(); $chkEnable=@(); $dtRun=@(); $btnRunNow=@(); $lblStatus=@()

for ($i=0; $i -lt 3; $i++) {
  $row = $i*2
  $tbl.RowStyles.Add( (New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::AutoSize)) )
  $tbl.RowStyles.Add( (New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::AutoSize)) )

  $lbl = New-Object System.Windows.Forms.Label
  $lbl.Text = "プログラム{0}:" -f ($i+1)
  $lbl.AutoSize = $true
  $tbl.Controls.Add($lbl, 0, $row)

  $t = New-Object System.Windows.Forms.TextBox
  $t.Anchor = "Left,Right"
  $t.Width = 500
  $tbl.Controls.Add($t, 1, $row)
  $txtPath += $t

  $b = New-Object System.Windows.Forms.Button
  $b.Text = "参照..."
  $tbl.Controls.Add($b, 2, $row)
  $btnBrowse += $b

  $c = New-Object System.Windows.Forms.CheckBox
  $c.Text = "有効"
  $tbl.Controls.Add($c, 3, $row)
  $chkEnable += $c

  $bn = New-Object System.Windows.Forms.Button
  $bn.Text = "今すぐ実行"
  $tbl.Controls.Add($bn, 4, $row)
  $btnRunNow += $bn

  $st = New-Object System.Windows.Forms.Label
  $st.Text = ""
  $st.AutoSize = $true
  $tbl.Controls.Add($st, 5, $row)
  $lblStatus += $st

  $lbl2 = New-Object System.Windows.Forms.Label
  $lbl2.Text = "実行日時:"
  $lbl2.AutoSize = $true
  $tbl.Controls.Add($lbl2, 0, $row+1)

  $dt = New-Object System.Windows.Forms.DateTimePicker
  $dt.Format = "Custom"
  $dt.CustomFormat = "yyyy/MM/dd HH:mm:ss"
  $dt.Width = 180
  $tbl.SetColumnSpan($dt, 2)
  $tbl.Controls.Add($dt, 1, $row+1)
  $dtRun += $dt
}

# 下部ボタン
$tbl.RowStyles.Add( (New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::AutoSize)) )
$btnSave = New-Object System.Windows.Forms.Button
$btnSave.Text = "保存"
$btnExit = New-Object System.Windows.Forms.Button
$btnExit.Text = "終了"
$pnl = New-Object System.Windows.Forms.FlowLayoutPanel
$pnl.FlowDirection = "LeftToRight"
$pnl.AutoSize = $true
$pnl.Controls.Add($btnSave)
$pnl.Controls.Add($btnExit)
$tbl.Controls.Add($pnl, 0, 6)
$tbl.SetColumnSpan($pnl, 6)
$form.Controls.Add($tbl)

# ===== トレイ動作 =====
$restore = {
  Write-Log "Tray: Open"
  $form.Show()
  $form.WindowState = "Normal"
  $form.ShowInTaskbar = $true
  if ($TrayOnlyWhenMinimized) { $script:tray.Visible = $false }
  $form.Activate()
}
$miOpen.add_Click($restore)
$script:tray.add_DoubleClick($restore)
$miExit.add_Click({
  $script:tray.Visible = $false
  $script:tray.Dispose()
  $form.Close()
})

$minHandler = {
  if ($form.WindowState -eq [System.Windows.Forms.FormWindowState]::Minimized) {
    Write-Log "Form minimized"
    $form.ShowInTaskbar = $false
    $form.Hide()
    $script:tray.Visible = $true
    try { $script:tray.ShowBalloonTip(800, "ミニスケジューラー", "通知領域で待機中です。", [System.Windows.Forms.ToolTipIcon]::None) } catch {}
  }
  elseif ($form.WindowState -eq [System.Windows.Forms.FormWindowState]::Normal) {
    Write-Log "Form normal"
    $form.ShowInTaskbar = $true
    if ($TrayOnlyWhenMinimized) { $script:tray.Visible = $false } else { $script:tray.Visible = $true }
  }
}
$form.Add_Resize($minHandler)
$form.Add_SizeChanged($minHandler)
$form.Add_ClientSizeChanged($minHandler)

$form.Add_FormClosing({
  $script:tray.Visible = $false
  $script:tray.Dispose()
})

# ===== 既存値 → UI(保存値そのまま)=====
for ($i=0; $i -lt 3; $i++) {
  $txtPath[$i].Text      = [string]$Jobs[$i].Path
  $chkEnable[$i].Checked = [bool]$Jobs[$i].Enabled
  if ($Jobs[$i].RunAt -is [datetime]) { $dtRun[$i].Value = $Jobs[$i].RunAt } else { $dtRun[$i].Value = Get-Date }
  $lblStatus[$i].Text    = [string]$Jobs[$i].Status
}

# ===== 参照/実行(Tag固定)=====
for ($i=0; $i -lt 3; $i++) { $btnBrowse[$i].Tag = $i; $btnRunNow[$i].Tag = $i }

foreach ($b in $btnBrowse) {
  $b.Add_Click({
    param($sender,$e)
    $idx = [int]$sender.Tag
    $ofd = New-Object System.Windows.Forms.OpenFileDialog
    $ofd.Filter = "すべてのファイル|*.*"
    if ($ofd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { $txtPath[$idx].Text = $ofd.FileName }
  })
}

function Invoke-Path([string]$filePath) {
  try {
    $psi = New-Object System.Diagnostics.ProcessStartInfo
    $psi.FileName = $filePath
    $psi.UseShellExecute = $true   # 既定の関連付け(ダブルクリックと同じ)
    [System.Diagnostics.Process]::Start($psi) | Out-Null
    return $true, ""
  } catch { return $false, $_.Exception.Message }
}

foreach ($b in $btnRunNow) {
  $b.Add_Click({
    param($sender,$e)
    $idx = [int]$sender.Tag
    $p = $txtPath[$idx].Text
    if ([string]::IsNullOrWhiteSpace($p)) { [System.Windows.Forms.MessageBox]::Show("パスを入力してください。") | Out-Null; return }
    $ok,$err = Invoke-Path $p
    if ($ok) {
      $Jobs[$idx].LastRun = (Get-Date).ToString("yyyy/MM/dd HH:mm:ss")
      $Jobs[$idx].Status  = "実行完了(手動)"
      $chkEnable[$idx].Checked = $false
      $Jobs[$idx].Enabled = $false
      Save-Jobs
      $lblStatus[$idx].Text = $Jobs[$idx].Status
      Write-Log ("RUN(NOW) ["+$p+"]")
    } else {
      [System.Windows.Forms.MessageBox]::Show("実行エラー:`n"+$err) | Out-Null
      Write-Log ("ERR(NOW) ["+$p+"]: "+$err)
    }
  })
}

# ===== 保存・終了 =====
$btnSave.Add_Click({
  for ($i=0; $i -lt 3; $i++) {
    $Jobs[$i].Path    = $txtPath[$i].Text
    $Jobs[$i].Enabled = $chkEnable[$i].Checked
    $Jobs[$i].RunAt   = $dtRun[$i].Value
  }
  Save-Jobs
  [System.Windows.Forms.MessageBox]::Show("保存しました。") | Out-Null
})
$btnExit.Add_Click({
  $script:tray.Visible = $false
  $script:tray.Dispose()
  $form.Close()
})

# ===== タイマー(1秒)=====
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 1000
$timer.Add_Tick({
  $now = Get-Date
  for ($i=0; $i -lt 3; $i++) {
    if (-not $Jobs[$i].Enabled) { continue }
    if ($now -ge [datetime]$Jobs[$i].RunAt) {
      $p = [string]$Jobs[$i].Path
      if ([string]::IsNullOrWhiteSpace($p)) {
        $Jobs[$i].Status = "パス未設定"; $Jobs[$i].Enabled = $false; $chkEnable[$i].Checked = $false
        Save-Jobs; $lblStatus[$i].Text = $Jobs[$i].Status; continue
      }
      $ok,$err = Invoke-Path $p
      if ($ok) {
        $Jobs[$i].LastRun = (Get-Date).ToString("yyyy/MM/dd HH:mm:ss")
        $Jobs[$i].Status  = "実行完了"
        $Jobs[$i].Enabled = $false
        $chkEnable[$i].Checked = $false
        Write-Log ("RUN(ON TIME) ["+$p+"]")
      } else {
        $Jobs[$i].LastRun = (Get-Date).ToString("yyyy/MM/dd HH:mm:ss")
        $Jobs[$i].Status  = "実行エラー"
        Write-Log ("ERR(ON TIME) ["+$p+"]: "+$err)
      }
      Save-Jobs
      $lblStatus[$i].Text = $Jobs[$i].Status
    }
  }
})
$timer.Start()

# ===== フォーム表示(標準メッセージループ)=====
[System.Windows.Forms.Application]::Run($form)
よかったらシェアしてね!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

目次