0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PCのスタートアップ時に LEDKeeper2 がクラッシュするのをどうにかする

Last updated at Posted at 2024-05-15

背景

 MSI Center の更新によってあるいはソフトの競合によってか、LEDKeeper2 が PC の起動後最初のログインでクラッシュする事象が多発しました。それによって信頼性モニターに✖印が付けまくられるのをどうにかしたいと思ったのが始まりです。まずは Mystic_Light_Service によって登録されるタスクに遅延を設定し様子を見ましたが、特に改善されませんでした。そこで LEDKeeper2 の直接起動が原因なんだと考え、PowerShell を経由する条件で作成したのが以下のスクリプトになります。

説明

 Mystic_Light_Service によって登録されるタスクは、Administrators に所属するユーザーがログオンしたタイミングでそのユーザーの権限に依って開始されるように構成されており、そのためには "タスクの実行時に使うユーザーアカウント" を Administrators グループとし、トリガーを "任意のユーザーのログオン時" に設定する必要がありました。必然的に "ユーザーがログオンしているかどうかにかかわらず実行する" が選択できなくなり、非インタラクティブにプログラムを実行する手段が一つなくなったことになります。また今回はスクリプトファイルを作りたくなかったので VBScript の使用も控えました。その条件下で白羽の矢が立ったのが conhost.exe の文書化されていないオプションである --headless です。将来的に削除されうる引数であり、個人的な趣味以外に使うのは推奨されませんが、今回は個人的な趣味の話なので使うことにしました。
 conhost.exe を使うことにしましたが、それによって生じる新たな問題もあります。PowerShell スクリプトブロック内の終了コードは PowerShell に戻り値として渡されますが、それを conhost.exe は継承しません。conhost.exe は PowerShell の終了コードに関係なく 0 をタスクスケジューラーに返します。この問題を解決するために外部から ExitCode を指定してプロセスを終了させる Win32 API の WTSTerminateProcess function を使う必要がありました(ちなみに conhost.exe を終了させれば、conhost.exe から起動された PowerShell も終了します)。この関数で PowerShell から親プロセス(conhost.exe)を ExitCode 指定して終了すると、その値がタスクスケジューラーに戻り値として渡され、ResultCode に反映されます。
 補足として、スクリプトの最後で LEDKeeper2.exe を強制終了しているのは、ここで終了させないと私の環境ではサインアウト時かシャットダウン時に LEDKeeper2 がクラッシュするためです。ログイン後に RGB ライティング機器を接続したときは手動で Mystic Light を起動してください。

手順

1."MSI Task Host - LEDKeeper2_Host" を無効化、Mystic_Light_Service を手動に

PowerShell:管理者
# タスクの無効化
Get-ScheduledTask -TaskName "*LEDKeeper2_Host*" | Disable-ScheduledTask

# サービスの手動化
Get-Service -Name 'Mystic_Light_Service' | Set-Service -StartupType 'Manual'

2.タスクスケジューラから直接実行できるよう Base64文字列 にエンコード

try・catchステートメントにおいてcatch{}ブロック内で終了例外が起こった時、同じcatch{}内の以降のコマンドは実行されません(PSVersion:5.1.22621.2506時点)。
$Call::WTSTerminateProcess()以降に “条件的に実行されるコマンド・ステートメントがない” または “直後にEXITがある” 必要があります。
WTSTerminateProcess関数について、
HANDLE hServer(ターミナルサーバーのハンドル)WTS_CURRENT_SERVER_HANDLE(C++)System.IntPtr.Zero(C#)[System.IntPtr]::Zero(PowerShell)
を設定すれば自分自身のセッションを指定することができます。

PowerShell:管理者
# エンコード
$GetBytes = [System.Text.Encoding]::Unicode.GetBytes({
  Function Terminate([Int]$ProcessId,[Int]$ExitCode){$Call::WTSTerminateProcess([System.IntPtr]::Zero,$ProcessId,$ExitCode)}
  $WTSTerminateProcess = @"
    [DllImport("Wtsapi32.dll", SetLastError = false, CharSet = CharSet.Auto)]
    public static extern bool WTSTerminateProcess(IntPtr hServer, Int32 ProcessId, Int32 ExitCode);
"@; $Call = Add-Type -Name "WtsApi" -Namespace "Win32" -MemberDefinition $WTSTerminateProcess -PassThru

  $ParentID = (Get-CimInstance -Class Win32_Process -Filter "ProcessId = $PID").ParentProcessId

  Switch -Exact ((Get-ScheduledTask -TaskName "*LEDKeeper2_Host*").State) { 
    $null {}
    Disabled {}
    default {
      Get-ScheduledTask -TaskName "*LEDKeeper2_Host*" | Disable-ScheduledTask
      Get-Service -Name 'Mystic_Light_Service' | Set-Service -StartupType 'Manual'
      Terminate -ProcessId $ParentID -ExitCode 32
      EXIT
    }
  }

  if (![System.IO.File]::Exists('C:\Program Files (x86)\MSI\MSI Center\Mystic Light\LEDKeeper2.exe')) {
    Terminate -ProcessId $ParentID -ExitCode 1920
    EXIT
  } elseif ((tasklist /fi "imagename eq LEDKeeper2.exe" /v /fo CSV /NH).Contains("Running")) {
    Terminate -ProcessId $ParentID -ExitCode 2182
    EXIT
  }

  $SetId = (Start-Process 'C:\Program Files (x86)\MSI\MSI Center\Mystic Light\LEDKeeper2.exe' -WindowStyle Hidden -PassThru).Id
  Start-Sleep -Seconds 100

  try { 
    Get-Process -Name "LEDKeeper2" -ErrorAction Stop
    Terminate -ProcessId $SetId -ExitCode 0
    [System.Environment]::Exit(!$?)
  } catch {
    Terminate -ProcessId $ParentID -ExitCode 1067
  }
})

$EncodedCommands = [Convert]::ToBase64String($GetBytes)

# デコード
# [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($EncodedCommands))

# この時点で実行できるか確認
# PowerShell -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -EncodedCommand $EncodedCommands; $LastExitCode

3.PowerShell からタスクを登録

conhost.exeの引数--headlessは使えなくなる可能性があります(使えるかどうかの確認は姉妹プログラム⦅OpenConsole.exe⦆の Arguments List から確認できるかもしれません)。

PowerShell:管理者
# 変数の宣言・代入
$GROUP = "Administrators"
$DirPath = "C:\WINDOWS\System32\WindowsPowerShell\v1.0\"

# タスク登録
$Action = New-ScheduledTaskAction `
  -Execute "C:\Windows\System32\conhost.exe" `
  -Argument "--headless PowerShell -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -EncodedCommand `"$EncodedCommands`" " `
  -WorkingDirectory "$DirPath"

$Trigger = New-ScheduledTaskTrigger -AtLogOn
$Trigger.Delay = [System.Xml.XmlConvert]::ToString((New-TimeSpan -Seconds 3))

$Settings = New-ScheduledTaskSettingsSet `
  -AllowStartIfOnBatteries `
  -Compatibility Win8 `
  -DisallowHardTerminate `
  -DontStopIfGoingOnBatteries `
  -DontStopOnIdleEnd `
  -ExecutionTimeLimit (New-TimeSpan -Hours 1) `
  -MultipleInstances IgnoreNew `
  -Hidden `
  -Priority 0 `
  -WakeToRun

$Principal = New-ScheduledTaskPrincipal `
  -GroupId "$GROUP" `
  -RunLevel Highest

$Component = New-ScheduledTask `
  -Action $Action `
  -Trigger $Trigger `
  -Settings $Settings `
  -Principal $Principal

Register-ScheduledTask -TaskName "User_LEDKeeper2" -InputObject $Component -AsJob -Force

4.タスクを手動で実行、プロセスに "LEDKeeper2 (32 ビット)" が出現し、少しして消えることを確認

PowerShell:管理者
# 一旦、プロセス:LEDKeeper2 を kill
(Get-Process -Name "LEDKeeper2" | Stop-Process -Force -Confirm:$false) 2>&1 > $null

# タスク実行
Get-ScheduledTask -TaskName "User_LEDKeeper2" | Start-ScheduledTask

# PowerShell から確認
$C = {Get-Process -Name "LEDKeeper2" -ErrorAction Stop}
$D = {try{.$C;Write-Host "Process is EXIST  $((++$i))" -F:Gre}catch{Write-Host "Process is not EXIST  $((++$i))" -F:R}}
1..53|%{.$D}{sleep 2;.$D}

5.タスクの実行結果を確認

$NumDaysミリ秒 を設定します。1日は86400000です。

PowerShell
$TaskPath = "User_LEDKeeper2"
$NumDays = 172800000
$Count = 6

$GetEvent = Get-WinEvent -MaxEvents $Count -LogName Microsoft-Windows-TaskScheduler/Operational -FilterXPath "*[
  System [
    Provider [@Name='Microsoft-Windows-TaskScheduler']
    and ( EventID=100 or EventID=201 )
    and TimeCreated [timediff (@SystemTime) <= $NumDays]
  ]
  and EventData [
    Data [@Name='TaskName'] 
    and (Data='\$TaskPath')
  ]
]"

[string[]]$PropertyQueries = @('Event/EventData/Data[@Name="TaskName"]','Event/EventData/Data[@Name="ResultCode"]')
$PropertySelector = [System.Diagnostics.Eventing.Reader.EventLogPropertySelector]::new($PropertyQueries) 
$TaskInvocations = foreach ( $EachEvent in $GetEvent ) {
  $TaskName,$ResultCode = $EachEvent.GetPropertyValues($PropertySelector)
  Switch -Exact ($EachEvent.Id) { 
    "100" {$ID = [String]$EachEvent.Id + ' (開始)'}
    "201" {$ID = [String]$EachEvent.Id + ' (停止)'}
  }
  if ([String]::IsNullOrWhiteSpace($ResultCode)) { 
    $ResultCode = 'N/A'
    $ExitCode   = 'N/A'
  } elseif ([bool]$ResultCode) {
    $ResultCode = '0x'+[convert]::ToString($ResultCode,16)
    $ExitCode   = $ResultCode - 0x80070000
  } else {
    $ExitCode   = $ResultCode
  }
  [PSCustomObject]@{
    タスク名   = $TaskName
    発生日時   = $EachEvent.TimeCreated
    イベントID = $ID
    戻り値     = $ExitCode
    終了コード = $ResultCode
  }
}

# "戻り値" は今回エンコードしたスクリプトの終了値で、
# "終了コード" は "戻り値" からタスクマネージャーが生成した値になります。

$TaskInvocations | Format-Table
conhost.exe 終了値 taskschd.msc への戻り値 エラーコード 説明
1920 2147944320 0x80070780 The file cannot be accessed by the system.( LEDKeeper2.exe が削除されている場合に表示)
0 0 0
0 0 0x0
0 2147942400 0x80070000 The operation completed successfully.
32 2147942432 0x80070020 The process cannot access the file because it is being used by another process.
1067 2147943467 0x8007042b The process terminated unexpectedly.
2182 2147944582 0x80070886 The requested service has already been started.
エラーコード算出式
$ExitCode = 2182
$ErrorCode = '0x'+[convert]::ToString(2147942400 + $ExitCode, 16)
$ErrorCode
PowerShell における10進数と16進数の対応表

長さが 8 の倍数である 16 進文字列の最初の桁が 8 以上の場合、数字は負の数として扱われます

10進数 16進数
-2147483648 0x80000000
-1 0xffffffff
0 0x00000000
2147483647 0x7fffffff
2147483648 0x80000000
4294967295 0xffffffff
PowerShell
'0x'+[convert]::ToString(0x80000000 * 0xffffffff, 16)

6.タスクの削除

Taskが指定されないでUnregister-ScheduledTask -Confirm:$falseが実行されると全タスクが削除されるため、ここではschtasks /deleteを使っています。

cmd or PowerShell:管理者
schtasks /delete -f /tn "User_LEDKeeper2"

参考

WTSTerminateProcess
WTSTerminateProcess 関数
WIn32API ターミナルサーバー上のプロセスを終了させる
リモート接続先で接続元の情報を C# で取得する
RDS-Manager.psm1

ScheduledTask
PowerShellでタスクスケジュール登録
ScheduledTasks Module | Microsoft Learn
PowerShellでタスクスケジューラを追加する詳細設定編
特定グループに所属する任意のユーザーがログインした時、タスクを実行する
Powershellでタスクスケジューラへタスク登録する際に、遅延時間を指定する方法

conhost.exe --headless
Palmer Eldrichの3つのブラウザ
Windows 11のコンソール処理について解説する

Get-WinEvent -FilterXPath
指定期間のイベントログを取得 [PowerShell]
Acting on exit code in Windows Task Scheduler
Task Scheduler - get history information into script variables

Error Codes
Windowsで表示されるエラーコードの見方
2.2 Win32 Error Codes
ErrorCodes_Final

以上

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?