走り書き

ちょこっとしたメモを残してく。 

LLM振り返り

人さまブログを要約して読んでいたら、あーちょっと書くかと。

~2024

・便利な壁打ち・検索代替期
課金しつつ「本当に毎日使うのか」と半信半疑で利用
用頻度が増加
検索代替から壁打ち用途へ移行
モデルの頻繁な更新そのものが、徐々に信頼に

 

~2025

・作業を依頼するが、丸投げじゃない期
課金して継続利用
要約・整理・壁打ちが日常化
スクリプト作成も依頼するが、2024年比で高度に盛られる
盛りが本当に必要かは疑問 〉code系ツールで担保したい
それよりも、ジョギングのパーソナルトレーナー委託という良い使い方を見つけた

 

2026~

・用途開拓期
人がやらなくてよい作業の刈り取りを主軸に用途開拓
Code系ツールでの模索
委託系の充実

Lenovo ThinkPad エッセンシャル 13/14インチ スリムトップロードケース

 ThinkPad エッセンシャル 13/14インチ スリムトップロードケースを買ってみた。

www.lenovo.com

この手のPCが収容できるバックは、13インチ想定か15インチかでサイズ感がガラッと変わる。

13インチ対応でもギリギリで入る仕様もあれば、15インチで大きすぎることも多い。回避するには、現物確認とかになるわけで、めんどくさい。

とは言いつつ、更新しないといけないわけで

ドン。写真は、即興で用意したリモコン付き。

サイズ感はこんな感じ。

PCバックとしては利用してないので、書類とか、傘、エコバックとかとか。入れたらそれなりな容量が欲しいので、用途としては、PC前提ではないのよね。

今回のこれ、高さ、奥行きともに問題なし。横幅は前のPCバックに比べると5㎝ぐらい大き目みたいなので、ワイドパネルのPC、テンキー付きとかを意識されてるのか。そんな大きさ。

そう書くと、時代とともにPCバックもサイズは変わってるのかもしれないけど、前のPCバックの値段は1万円越え、こちらは何と数千円。安い!!!。壊れたら買いなおせばよい。

ご参考になれば、これ幸い。

DP経由でつないでるスピーカーがスリープ回復時に死亡する件

問題はこれ

learn.microsoft.com

けど、Windows11は解消してるようなのですが、どうなの。

で、以下の実装で、コンパのサウンド設定を開いて閉じるスクリプトを書き直したけど、テストしたら解消٩(*´ᗜ`)ㅅ

メモとして、トリガーはロック解除時にスクリプトを実行。実行はpowershell

本体のスクリプト

設定値を設定>コンパネのサウンド設定を探してあれば終了>無ければサウンド設定を開く>閉じる。

下のコードをコピーして、適当な名前で保存

# コンパネからサウンド設定を呼び出し閉じる
# .\Reset-SoundCpl.ps1 -DelayBeforeReopenSeconds 1 -DelayBeforeFinalCloseSeconds 1

[CmdletBinding()]
param(
  [int]$DelayBeforeReopenSeconds = 1,        # 再起動までの待機秒数(既定 5)
  [int]$DelayBeforeFinalCloseSeconds = 1,    # 再起動後に閉じるまでの待機秒数(既定 3)
  [switch]$FinalClose = $true                # 既定で自動クローズ
)

function Get-SoundCplProcesses {
  Get-CimInstance Win32_Process | Where-Object {
    $_.Name -in @('rundll32.exe','control.exe') -and
    ($_.CommandLine -match '(?i)mmsys\.cpl')
  }
}

function Stop-SoundCplProcesses {
  param([Parameter(Mandatory=$true, ValueFromPipeline=$true)][object[]]$Processes)
  process {
    foreach ($p in $Processes) {
      try {
        Stop-Process -Id $p.ProcessId -Force -ErrorAction Stop
        Write-Host "Killed PID=$($p.ProcessId) Name=$($p.Name)"
      } catch {
        Write-Warning "Stop-Process failed for PID=$($p.ProcessId): $_"
      }
    }
  }
}

function Start-SoundCpl {
  $cpl = Join-Path $env:WINDIR 'System32\mmsys.cpl'
  try {
    Write-Host "Starting Sound CPL via rundll32..."
    Start-Process -FilePath (Join-Path $env:WINDIR 'System32\rundll32.exe') `
                  -ArgumentList "shell32.dll,Control_RunDLL `"$cpl`", 0" `
                  -WindowStyle Normal
  } catch {
    Write-Warning "rundll32 での起動に失敗:$_  control.exe 経由に切り替えます。"
    try {
      Start-Process -FilePath (Join-Path $env:WINDIR 'System32\control.exe') `
                    -ArgumentList "mmsys.cpl" `
                    -WindowStyle Normal
    } catch {
      throw "mmsys.cpl の起動に失敗しました:$_"
    }
  }
}

function Wait-ForNewSoundCpl {
  param([int]$TimeoutMs = 3000, [int]$IntervalMs = 200)
  $deadline = [DateTime]::UtcNow.AddMilliseconds($TimeoutMs)
  while ([DateTime]::UtcNow -lt $deadline) {
    $procs = Get-SoundCplProcesses
    if ($procs) { return $procs }
    Start-Sleep -Milliseconds $IntervalMs
  }
  return $null
}

# 1) 既存の mmsys.cpl セッションがあれば終了
$existing = Get-SoundCplProcesses
if ($existing) {
  Write-Host "Found $(($existing | Measure-Object).Count) Sound CPL process(es). Closing..."
  $existing | Stop-SoundCplProcesses
} else {
  Write-Host "No existing Sound CPL processes found."
}

# 2) 指定秒数待機
Write-Host "Waiting $DelayBeforeReopenSeconds second(s) before reopen..."
Start-Sleep -Seconds $DelayBeforeReopenSeconds

# 3) 再起動
Start-SoundCpl

# 4)(既定で有効)再度検索して一定時間後に終了
if ($FinalClose) {
  Write-Host "Waiting $DelayBeforeFinalCloseSeconds second(s) before final close..."
  Start-Sleep -Seconds $DelayBeforeFinalCloseSeconds

  $after = Wait-ForNewSoundCpl -TimeoutMs 3000 -IntervalMs 200
  if ($after) {
    Write-Host "Final close of Sound CPL..."
    $after | Stop-SoundCplProcesses
  } else {
    Write-Host "Sound CPL not found for final close (timeout)."
  }
}

 

で、これをどう起動させるのか。

www.intellilink.co.jp

こちらの記事をオマージュし、記事中のStartPs1.vbsをそのまま利用

書いてる内容をコピーして、メモ帳に張り付けて保存。

 

タスクスケジューラを起動して、新規にタスクを作成

トリガータブを開いて、タスクの開始:ワークステーションアンロック時、有効などにしてもらう。

操作タブも開いて新規にて作成し、操作:プログラムの開始、プログラム:wscript.exe。

引数は「//B "C:\apps\StartPs1.vbs" "C:\apps\sound-on.ps1"」先ほど保存したファイルにして閉じる。

画面をロックして、スリープは、PCの設定がそれ用になってないとスリープにならなさそうとか思ったけど、まあ…。

で、無事スリープが出来ましたら、解除して瞬間コンパネのサウンドがちらっと映ったらOK。

おしまい。

 

AD配下のPCイベントログを転送してみた

初見殺しだけど、ワークステーションとかでやるともっと大変なんだとか。

ベースの手順 

www.yubion.com

〇ソースマシン(転送元)

1.ソースマシンで次のコマンドを管理者権限で実行
winrm quickconfig
2.ローカルユーザーとグループ」を開き、「グループ」の「Event Log Readers」を開く
lusrmgr.msc
# オブジェクトの種類の選択で「コンピューター」を選んでください
 
3.「Event Log Readers」にコレクトと「Network Service」を追加
# Windows11の場合、場所を自分PCにして登録
# DCの場合、AD上のbuiltinにあるグループに追加、追加後「gpupdate /force」
 
4.以下のコマンドにてchannelAccessの値をコピー
wevtutil gl Security 
5.コピーした内容をもとに、「(A;;0x1;;;NS)」を末尾に追加して実行
wevtutil sl Security /ca:"O:BAG:SYD:(A;;0xf0005;;;SY)(A;;0x5;;;BA)(A;;0x1;;;S-X-X-XX-XXX)(A;;0x1;;;NS)"
 
6.プロキシ環境下は、例外設定でコレクト側PCのホスト名も追加してください
# エラー対応
netsh winhttp show proxy
inetcpl.cpl
netsh winhttp import proxy source=ie
netsh winhttp show proxy
net stop winrm
net start winrm

# 名称解決するかお手軽に確認する方法としては

# イベントビュワー(eventvwr)のサブスクリプションを新規に作成
# コレクトのPCを登録してテスト

# ソース側ではこちらの登録はしないので、確認ができたらキャンセルで逃げます
 
7.こちらはこれでおしまい
 

★コレクト(ログを収集)

1.コレクトのPCで以下のコマンドを管理者にて実行
wecutil quick-config
#問われたらYで進む
 
2.イベントビュワーを開く
eventvwr
 
3.サブスクリプションを作成し、ソースになるコンピュータを登録してテスト
# エラーになる場合は、プロキシの例外設定を確認
netsh winhttp show proxy
inetcpl.cpl
netsh winhttp import proxy source=ie
netsh winhttp show proxy
net stop winrm
net start winrm
 
# 後扱いが面倒になるので英数字のみにするとよいかと
 
5.イベントの選択をクリック
  • イベントレベルから全部選ぶ
  • ログごとから、Windowsのログの「Application,セキュリティ,システム」を選ぶ
#無事稼働が出来たら、何を集計して監視とか、そのものを監視するのか調整を
 
6.入力が完了したらOKで閉じて、次のコマンドを入力
# サブスクリプション名は、先ほど入力した名前です

7.設定はこれで以上です

 

■確認

1.転送内容を確認する

コレクトのイベントビュワーを開いて、セキュリティまで転送されてるかを確認
# コンピュータ名を追加したい場合は、タイトルのところを右クリックにて追加で

 

2.Powershellにて確認する

Get-WinEvent -LogName "ForwardedEvents" | Select-Object -First 10

# 最初の10件だけ表示する

 

〇その他

Windows Server 2012 R2、Windows Server 2016、Windows Server 2020で確認。

ADがあれば入れてよいけど、NXlogで集約してもよいのでないかとは思った。

ハードウェア、アプリを追うのか、セキュリティ追うのかは区別したほうがよい。

詳細、何が欲しいのかは、この作業とは別なので、がんばってください。

Get-MessageTraceDetailV2はどんな結果が出るの

Exchange Online のトレース画面からプロパティをポチポチ見るのも悪くないのですが、「もう PowerShell で完結」というとき用のメモです。

コマンドレットで完結すると、powershell5.1を開いて以下の内容を実行。

# コマンドレットが無ければ、こちらの手順で

Connect-ExchangeOnline

#時間はUTCで+9してください
$StartDate = [datetime]"2025-12-10 09:00"
$EndDate   = [datetime]"2025-12-11 09:00"
$Interval  = 1 # 時間単位
$Results   = @()

while ($StartDate -lt $EndDate) {
    $NextEnd = $StartDate.AddHours($Interval)
    if ($NextEnd -gt $EndDate) { $NextEnd = $EndDate }

    $Batch = Get-MessageTraceV2 -StartDate $StartDate -EndDate $NextEnd
    $Results += $Batch

    $StartDate = $NextEnd
}

$Results | Export-Csv ".\MessageTrace.csv" -Encoding UTF8 -NoTypeInformation

実行したディレクトリにファイルが出来るので開く。開くと一覧になってるので、statusを確認し、見てみたい内容の選ぶと。

statusはこんな値

  • Delivered:正常に配信
  • Expanded:配布グループの展開
  • Failed:宛先に配信NG
  • FilteredAsSpam:スパム判定
  • Quarantined:メッセージの隔離

Quarantinedなメールがあったので、こちらのコマンドレットを実行

Get-MessageTraceDetailV2 -MessageTraceId xxxxxxxxxx-xxx-xxx-xxx-xxxxxxxx -RecipientAddress hoge@hogehoge.com

 

Date                   Event                Detail
----                   -----                ------
2025/11/24 22:47:48    受信                 XXXXXXXXXXXXXXXX.XXX.OUTLOOK.COM でメッセージが受信されました。
2025/11/24 22:47:50    スパム               スパム信頼度レベル: 8
2025/11/24 22:47:51    配信                 メッセージはフォルダー DefaultFolderType:QuarantinedEmailSecured に正常...

と内容は確認できます。

LLMで上手に組み合わせたら、らしいスクリプトがかけるけど、リアルタイムに見たいのか。その前に、statusの件数を数え、増えたら詳細を得る運用でもよさそうだ。そこまで見るのかと思ったら、DMARC対策用にリストにしてもよさそうね。

あと自動化したい場合は、Entra ID にアプリとして登録して認証をパススルーしてやってください。

 

Windowsでuptimeを確認して、イベントログとsyslogに転送する

更新ファイルが未適用のままのサーバが、再起動できない状態でしばらく放置されていました。監視できてないので、ログ見ろって話にするか、別な方法を取るか。

更新ファイル適用したら、バグもなくなり再起動されるのでは?とか。

どこまでやったんだろう…。

 

こちらを.ps1ファイルにしてタスクスケジューラから実行。

uptimeが3時間以内なら情報として取り扱い、3時間以内ならエラーとして対処。

動きとしてはそうなので、再起動後に試すように配置すればよろしい。

 

# ============================================================
# 設定(ここだけ触れば運用に合わせて調整)
# ============================================================
$Config = @{
    SyslogServer   = "192.168.0.10"
    SyslogPort     = 514

    ThresholdHours = 3.0  # 3時間を超えたら Error

    Facility       = "local0"  # RFC5424 facility

    Flag           = 0  # Bit判定用(1 → 強制Error)
    ForceError     = $false

    EventSource    = "UptimeMonitorScript"
    EventLogName   = "Application"

    AppName        = "UptimeMonitor"
    MsgId          = "UPTIME_CHECK"
}
# ============================================================


# -----------------------------
# 関数: uptime 取得
# -----------------------------
function Get-Uptime {
    $os = Get-CimInstance -ClassName Win32_OperatingSystem
    return (Get-Date) - $os.LastBootUpTime
}

# -----------------------------
# PRI 計算
# -----------------------------
function Get-SyslogPri {
    param(
        [Parameter(Mandatory=$true)][string]$FacilityName,
        [Parameter(Mandatory=$true)][ValidateSet("info","error")] [string]$SeverityName
    )

    $facilityCodes = @{
        "local0" = 16; "local1" = 17; "local2" = 18; "local3" = 19
        "local4" = 20; "local5" = 21; "local6" = 22; "local7" = 23
    }
    $severityCodes = @{ "error" = 3; "info" = 6 }

    return ($facilityCodes[$FacilityName] * 8 + $severityCodes[$SeverityName])
}

# -----------------------------
# RFC5424 syslog 送信
# -----------------------------
function Send-SyslogRfc5424 {
    param(
        [int]$Pri, [string]$AppName, [string]$ProcId,
        [string]$MsgId, [string]$StructuredData,
        [string]$Message, [string]$Server, [int]$Port
    )

    $version   = 1
    $timestamp = (Get-Date).ToString("yyyy-MM-dd'T'HH:mm:ss.fffK")
    $hostname  = $env:COMPUTERNAME

    $syslog = "<$Pri>$version $timestamp $hostname $AppName $ProcId $MsgId $StructuredData $Message"

    $client = New-Object System.Net.Sockets.UdpClient
    try {
        $bytes = [System.Text.Encoding]::UTF8.GetBytes($syslog)
        [void]$client.Send($bytes, $bytes.Length, $Server, $Port)
    }
    finally {
        $client.Close()
    }
}


# -----------------------------
# メイン処理
# -----------------------------
$uptime = Get-Uptime
$uptimeHours = [math]::Round($uptime.TotalHours, 2)
$uptimeSeconds = [math]::Round($uptime.TotalSeconds)

$evaluation = if ($uptimeHours -ge $Config.ThresholdHours) { "OverThreshold" } else { "WithinThreshold" }

# Error 判定
$severity =
    if ($Config.ForceError -or *1 {
        "error"
    }
    else {
        if ($uptimeHours -ge $Config.ThresholdHours) { "error" } else { "info" }
    }

# メッセージ
$message = "Uptime: {0:F2}h ({1}s). Threshold: {2}h. Eval: {3}. Severity: {4}. Flag: {5}." -f `
    $uptimeHours, $uptimeSeconds, $Config.ThresholdHours, $evaluation, $severity, $Config.Flag


# イベントログ
if (-not [System.Diagnostics.EventLog]::SourceExists($Config.EventSource)) {
    try { New-EventLog -LogName $Config.EventLogName -Source $Config.EventSource } catch {}
}

# severity に応じて EntryType など設定
$entryType = if ($severity -eq "error") {
    [System.Diagnostics.EventLogEntryType]::Error
} else {
    [System.Diagnostics.EventLogEntryType]::Information
}

# ここで EventId を決める(PS5.1対応)
$eventId = if ($severity -eq "error") { 20001 } else { 20000 }

Write-EventLog -LogName $Config.EventLogName -Source $Config.EventSource `
    -EventId $eventId `
    -EntryType $entryType `
    -Message $message


# syslog PRI / SD
$pri = Get-SyslogPri -FacilityName $Config.Facility -SeverityName $severity
$sd  = ('[uptime@32473 uptime="{0:F2}" seconds="{1}" threshold="{2}" eval="{3}" flag="{4}"]' -f `
        $uptimeHours, $uptimeSeconds, $Config.ThresholdHours, $evaluation, $Config.Flag)

# 送信
Send-SyslogRfc5424 `
    -Pri $pri `
    -AppName $Config.AppName `
    -ProcId $PID `
    -MsgId $Config.MsgId `
    -StructuredData $sd `
    -Message $message `
    -Server $Config.SyslogServer `
    -Port $Config.SyslogPort

 

*1:$Config.Flag -band 0x1) -ne 0