はじめに
仕事の日報をMarkdownで書き始めることにした。理由は2つある。
- プレーンテキストなので、気軽に書ける
- 後からAI(Claudeなど)に読み込ませて、作業内容の要約や振り返りに活用しやすい
ただし、自分の職場にはいくつか制約がある。
- Obsidianなど、新しいアプリケーションの利用許可が出ていない
- 日報はOneDrive配下に保存したい
- 利用できるエディタの中では、汎用性の高いVS Codeを使いたい
そこで、VS Codeで日報フォルダを開くと、今日の日報が自動で作成されて開く仕組みを作った。
簡単にできると思っていたのだが、OneDrive配下のパスを扱うところでつまずき、思ったより作り込みが必要になった。
今回は、実装方法だけでなく、最初の実装で失敗した理由や、設計時に考えたことも備忘録として残しておく。
動作環境
- Windows 11
- Windows PowerShell 5.1
- VS Code 1.141.0
- OneDrive(個人用・会社用)
完成形
実現した機能は次の2つ。
- VS Codeで日報フォルダを開くと、テンプレートから当日分の
MMDD.mdを作成し、自動で開く - 月が変わったら、月次整理タスクを実行して、当月以外の日報を
archive/YYYY/MM/に移動する
当月分の日報はフォルダ直下に置き、過去の日報は年月別のフォルダに整理する構成にした。
試作:ワンライナーで実装する
最初は、tasks.json にPowerShellの処理を直接書いて済ませようとした。
tasks.json
初期版は、概ね次のような構成だった。
{
"version": "2.0.0",
"tasks": [
{
"label": "今日のファイル作成",
"type": "shell",
"command": "$date = Get-Date -Format \"MMdd\"; $file = ${workspaceFolder}\\$date.md; Copy-Item ${workspaceFolder}\\_template\\daily.md $file"
}
]
}
日付を取得し、テンプレートをコピーするだけなので、これで十分だと思っていた。
起きたこと
実行すると、次のようなエラーが発生した。
用語 '日記用ワークスペーステスト' は、
コマンドレット、関数、スクリプト ファイル、
または実行可能プログラムの名前として認識されません。
エラー発生時のパスを確認すると、OneDrive配下のディレクトリ名に空白が含まれていた。
例えば、会社用のOneDriveでは、次のようなパスになることがある。
C:\Users\<ユーザー名>\OneDrive - <組織名>\<日報フォルダ>
失敗原因
原因はOneDriveそのものではなく、空白を含むパスを、引用符などで適切に保護せずにシェルコマンドへ埋め込んでいたことだった。
シェルタスクでは、コマンド文字列をPowerShellなどのシェルに渡して実行する。そのため、パスの組み立てや引用符の扱いを誤ると、コマンドが意図したとおりに解釈されない。
最初の実装では、この点を考慮できていなかった。
ワンライナーの引用符を調整する方法もあるが、今回は処理をスクリプトファイルに切り出し、タスクから引数として呼び出す構成に変更した。
プロジェクト構成
最終的な構成は次のとおり。
<日報フォルダ>/
├─ .vscode/
│ ├─ tasks.json
│ └─ scripts/
│ ├─ new-daily.ps1
│ └─ archive-monthly.ps1
├─ _template/
│ └─ daily.md
├─ archive/
│ └─ 2026/
│ └─ 09/
│ └─ 0930.md
└─ 1013.md
各ファイルの役割は次のとおり。
| ファイル | 役割 |
|---|---|
.vscode/tasks.json |
VS Codeのタスクを定義する |
new-daily.ps1 |
当日分の日報を作成し、VS Codeで開く |
archive-monthly.ps1 |
当月以外の日報をアーカイブへ移動する |
_template/daily.md |
日報のテンプレート |
archive/ |
過去の日報の保存先 |
コード
tasks.json
.vscode/tasks.json の内容は次のとおり。
{
"version": "2.0.0",
"tasks": [
{
"label": "作業記録: 今日のファイル作成",
"type": "process",
"command": "powershell.exe",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${workspaceFolder}\\.vscode\\scripts\\new-daily.ps1",
"-Root",
"${workspaceFolder}"
],
"presentation": {
"reveal": "silent",
"panel": "shared",
"clear": true
},
"problemMatcher": [],
"runOptions": {
"runOn": "folderOpen"
}
},
{
"label": "作業記録: 月次整理",
"type": "process",
"command": "powershell.exe",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${workspaceFolder}\\.vscode\\scripts\\archive-monthly.ps1",
"-Root",
"${workspaceFolder}"
],
"presentation": {
"reveal": "always",
"panel": "shared",
"clear": true
},
"problemMatcher": []
}
]
}
ポイントは、type に process を指定し、実行するコマンドと引数を分けていること。
また、runOn に folderOpen を指定することで、フォルダを開いたときに日報作成タスクが実行されるようにした。
月次整理タスクには runOn を指定していないため、自動では実行されない。必要なタイミングで手動実行する想定だ。
new-daily.ps1
.vscode/scripts/new-daily.ps1 では、当日分の日報を作成する。
param([Parameter(Mandatory)][string]$Root)
$ErrorActionPreference = 'Stop'
$now = Get-Date
$path = Join-Path $Root ($now.ToString('MMdd') + '.md')
if (-not (Test-Path -LiteralPath $path)) {
$tplPath = Join-Path $Root '_template\daily.md'
$tpl = [IO.File]::ReadAllText($tplPath)
$content = $tpl.Replace(
'{{DATE}}',
$now.ToString('yyyyMMdd (ddd)')
)
[IO.File]::WriteAllText($path, $content)
}
if (Get-Command code -ErrorAction SilentlyContinue) {
code -r $path
}
処理の流れはシンプルだ。
- 現在の日付から
MMDD.mdというファイル名を作る - ファイルが存在しなければ、テンプレートを読み込む
-
{{DATE}}を当日の日付に置き換えて保存する -
code -rでファイルを開く
既にファイルが存在する場合は上書きしない。そのため、VS Codeで日報フォルダを開き直しても、過去に書いた内容が消えることはない。
なお、最後の処理で使用する code コマンドが実行できるよう、VS Codeのコマンドラインが利用可能な環境を前提としている。
archive-monthly.ps1
.vscode/scripts/archive-monthly.ps1 では、当月以外の日報をアーカイブへ移動する。
param([Parameter(Mandatory)][string]$Root)
$ErrorActionPreference = 'Stop'
$now = Get-Date
foreach ($f in Get-ChildItem -LiteralPath $Root -File) {
if ($f.Name -notmatch '^(\d{2})\d{2}\.md$') {
continue
}
$month = [int]$Matches[1]
# 当月分はルートに残す
if ($month -eq $now.Month) {
continue
}
# MMDDだけでは年を特定できないため、月から推定する
# 当月より大きい月は前年、それ以外は今年とみなす
$year = $now.Year - [int]($month -gt $now.Month)
$archiveDir = Join-Path $Root "archive\$year\$($month.ToString('00'))"
[void][IO.Directory]::CreateDirectory($archiveDir)
$dest = Join-Path $archiveDir $f.Name
if (Test-Path -LiteralPath $dest) {
Write-Warning "skip (exists): $dest"
continue
}
$f.MoveTo($dest)
Write-Output "moved: $($f.Name) -> $dest"
}
ルート直下のファイルを走査し、MMDD.md という形式に一致するものだけを整理対象にしている。
例えば、10月に実行した場合、次のようになる。
| ファイル | 処理 |
|---|---|
1013.md |
当月分なのでルートに残す |
0930.md |
archive/2026/09/0930.md へ移動 |
1105.md |
前年分と推定し、archive/2025/11/1105.md へ移動 |
README.md |
日報の命名規則に一致しないため対象外 |
移動先のファイルが既に存在する場合は、上書きせず警告を出してスキップする。
ただし、ファイル名に年が含まれていないため、年の判定はあくまで推定である。この制約については、後述する。
テンプレート
_template/daily.md の内容は次のとおり。
{{DATE}}
# 今日やること
- [ ]
# 作業ログ
# メモ・課題
# 明日への引き継ぎ
現時点では仮のテンプレートだ。
日付欄の {{DATE}} をスクリプトで置き換えるようにしているが、項目の構成は実際に使いながら改善していく予定。
OneDriveのパスで壊れないようにした点
今回、特に意識した点を整理しておく。
1. type: process でシェルを介さずに実行する
シェルタスクでは、コマンドがシェルによって解釈される。そのため、コマンド文字列の組み立て方や引用符の扱いによっては、空白を含むパスなどを正しく扱えない。
一方、プロセスタスクでは、実行するプログラムを command に、引数を args に分けて指定できる。
今回のように、PowerShellスクリプトへパスを渡して処理する場合は、こちらのほうが構成をシンプルにできる。
参考:VS Code公式ドキュメント:Integrate with External Tools via Tasks
2. -LiteralPath と.NETのファイルAPIを使う
PowerShellの -Path は、パスにワイルドカードを含めて指定できる。
そのため、角括弧 [ ] などを含むパスは、ワイルドカードとして解釈される可能性がある。
今回のスクリプトでは、Test-Path や Get-ChildItem に -LiteralPath を指定し、パスを文字列どおりに扱うようにした。また、ファイルの読み書きには .NET の System.IO.File APIを使用している。
OneDriveのパスに限った対策ではないが、ユーザー名や組織名、保存先などによってパスの形式が変わっても扱いやすくなる。
3. 日報ファイルの文字コードをUTF-8・BOMなしにする
日報はMarkdownファイルなので、文字コードも明示的に扱うことにした。
Windows PowerShell 5.1では、Set-Content の既定のエンコーディングは、通常、システムのANSIコードページに対応する。一方、.NET の [IO.File]::WriteAllText(path, content) は、既定でBOMなしのUTF-8として書き込む。
そのため、今回は WriteAllText を使って日報を保存している。
参考:Microsoft公式ドキュメント:about_Character_Encoding(Windows PowerShell 5.1)
なお、ここで説明しているのは日報ファイルの出力文字コードについてだ。PowerShellスクリプト自体に日本語などの非ASCII文字を記述する場合は、Windows PowerShell 5.1がスクリプトの文字コードをどう解釈するかについても注意が必要になる。
使う前に確認すること
職場の環境によっては、次の理由で動かない可能性がある。
-
実行ポリシー:組織のグループポリシーで実行ポリシーが設定されている場合、
-ExecutionPolicy Bypassでは回避できないことがある。Get-ExecutionPolicy -Listで確認する。 -
自動タスクの許可:VS Codeで自動タスクの実行が許可されている必要がある。ワークスペースが信頼されていない場合なども自動実行されない。必要に応じて
task.allowAutomaticTasksを確認する。
今後
今後、時間があれば次の点も改善していきたい。
- 月単位ではなく、作成から30日以上経過したファイルをアーカイブする仕組み
- AIを使って日報を要約し、作業内容や課題を振り返る仕組み
特にAIによる振り返りは、日報をMarkdownで残すことにした理由の一つなので、実際の記録が蓄積してから試してみたい。
おわりに
すぐ終わるだろと思って始めた日報作成タスクが、思ったよりも大変で困った。やはりファイル名に変な文字を入れるのは許してはならない。
あと、markdown自体は前から使っていたけど、これで日記や日報を書くのは初めてなので、実際使いやすいのかは今後1,2か月で見ていくつもり。
