真夏の怪異、ログオン中のユーザーが1人少ない! それは「Shift-JIS」の呪い?その知識、ホントに正しい? Windowsにまつわる都市伝説(189)

先日、Windows Server用のWebベースの管理ツール「Windows Admin Center」を操作していて、おかしな表示に気が付きました。「概要」ページにある「ログインしているユーザー」が「-1」だというのです。サーバに1人以上のユーザーがログオンしている場合、今度は「0」だというのです。一体どういうことでしょうか。なぜこんな凡ミスにこれまで誰も気が付かなかったのかと疑問に思っていたら、実は日本語環境特有の「Shift-JIS」の呪いだったのです。

» 2021年07月14日 05時00分 公開
[山市良テクニカルライター]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

「Windowsにまつわる都市伝説」のインデックス

Windowsにまつわる都市伝説

「ログインしているユーザー」が「-1」は仕様? 「0」はバグ?

 「Windows Admin Center」の「概要」ページにある「ログインしているユーザー」の数が実際とは異なる問題に気が付いたのは、2021年5月末にリリースされた最新バージョン「2103.2」に更新してすぐのことでした。

 管理対象のサーバにログオン中のユーザーがいないときには「-1」と報告し(画面1)、1人のユーザーがログオン中のときには「0」を報告します。さらに、ローカルログオンとリモートデスクトップ接続で同時に複数のユーザーでログオンしてみても、「0」を報告するのです(画面2)。

画面1 画面1 Windows Admin Centerの「概要」ページを開くと、「ログインしているユーザー」が「-1」というおかしな表示に気が付く
画面2 画面2 管理対象に1人以上のユーザーがログオン中の場合、「ログインしているユーザー」は「0」を報告。これもおかしい

 まだ最新版に更新していないバージョン「2103」で確認しても同様です。UIの言語設定の問題かと思い、「設定」の「言語/地域」を「日本語/日本語(日本)」から「English/English(United States)」に変更してみましたが、状況は変わりません。

 Azureポータルに統合された「Windows Admin Center(プレビュー)」でも確認してみました。管理対象のサーバにログオン中のユーザーがいないときに「-1」と報告するのは同じでしたが(画面3)、1人以上のユーザーのログオン数は正しく報告しているように見えます(画面4)。

画面3 画面3 AzureポータルのWindows Admin Center(プレビュー)でも「ログインしているユーザー」が「-1」という表示は同じ
画面4 画面4 AzureポータルのWindows Admin Center(プレビュー)の方は、1人以上のログオン中のユーザー数は正しい

 ログオン中のユーザーがいないときに「-1」を報告するのは、どうやら仕様のようです。1人以上のログオンユーザーがいる場合に「0」を報告する問題の原因は、管理対象のサーバの言語環境、それも日本語環境固有の問題のようです。

 オンプレミスのWindows Admin Centerの接続先は日本語版「Windows Server 2019評価版」でシステムロケールはそれぞれ「日本語(日本)」、AzureポータルのWindows Admin Center(プレビュー)の接続先は英語版「Windows Server 2019 Datacenter:Azure Edition」でシステムロケールは「English(United States)」という違いがあります。

誰もいないときの「-1」は言語に関係ない初歩的なバグ

 Windows Admin Centerには現在、表示中のツール(ページ)で使用されているPowerShellスクリプトを表示する機能があります(※Azureポータルに統合されたWindows Admin Center《プレビュー》にこの機能はありません)。

 「概要」ページの「ログオンしているユーザー」の情報は、「Get-NumberOfLoggedOnUsers」ファンクションとして実装されていました(画面5)。コードを追いかけてみると、システムにログオンしているユーザーの情報を表示する「quser.exe」コマンドを実行し、その標準出力の行数からヘッダ行の「1」を引いたものをログオン中のユーザー数としていました。

画面5 画面5 「Get-NumberOfLoggedOnUsers」ファンクションでは、quser.exeの標準出力の行数からヘッダ行分の「1」を引いてユーザー数を得ている

 quser.exeはログオン中のユーザーがいない場合、標準エラーに「* に対するユーザーは存在しません。」(英語環境の場合「No User exists for *」)を出力します。

 「Get-NumberOfLoggedOnUsers」ファンクションでは、標準エラーに出力があればカウントの初期値「0」($count = 0)を返すことを期待しているようですが、以下のコードでは標準エラーを判定する前にプロセスを閉じてしまっているため、標準エラーは判定前に空の状態になってしまうのではないでしょうか。標準出力もまた空っぽ(0行)のため、ユーザー数が「-1」(0-1)になるというわけです。

function Get-NumberOfLoggedOnUsers {
$count = 0
$error.Clear();
〜中略〜
$process.WaitForExit()
$process.Dispose()
if (!$process.StandardError)
{
    # quser does not return a valid ps object and includes the header.
    # subtract 1 to get actual count.
    $count = $result.count - 1
}
@{Count = $count}
}

 プロセスの終了と標準エラーの判定の順番を変更することで、コードが期待した結果を出すことは、誰もログオンしていないHyper-V仮想マシンに対するPowerShell Direct(「Invoke-Command -VMName 仮想マシン名」)でスクリプトを実行させて確認しました(画面6)。

画面6 画面6 プロセスの終了と標準エラーの判定の順番を入れ替えることで、ユーザー数「0」を正しく取得できることを、Hyper-V仮想マシンのPowerShell Direct機能で確認

 これは明らかなバグなので、Windows Admin Center側で修正される必要があります。それにしても、正式リリースのバージョンなのに、テストしていないのでしょうか。なお、ログオン中のユーザーがいると考慮した場合、順番を入れ替えるだけではなく、もう少し修正が必要です(正しく動作するコードは本稿の最後に掲載)。

1人以上が「0」は日本語固有のバグ

 コマンドプロンプトやPowerShellウィンドウでquser.exeを実行してみると、日本語固有の問題が影響していることが容易に想像できました。前出の画面6とは異なり、ヘッダ行が2行で表示されるからです。コマンドプロンプトで「CHCP 437」と入力し、英語のコードページに切り替えて実行してみると、日本語環境で2行に分かれている「アイドル時間」の部分は、英語環境では1行の「IDLE TIME」です(画面7)。

画面7 画面7 日本語環境では出力結果の行数-1でユーザー数が得られるはずもない

 日本語環境ではヘッダ行が2行に分かれているわけですから、1人以上のログオンユーザーがいる場合、「実際のユーザー数+1」の数を報告しそうですが、実際には全て「0」と報告します。なぜでしょう。

 システムロケール「日本語(日本)」と「英語(米国)」(日本語版のシステムロケールを単純に変更しただけ)のそれぞれの環境で、PowerShellスクリプトの該当するコードを直接実行してみました。

 システムロケール「日本語(日本)」の方は、標準出力を1行ずつ読み取っている次のコードが、1行目の「アイドル」のところで読み込みをストップしているのが分かりました(画面8)。結果として、日本語環境では1人以上のログオンユーザーを常に「0」(1-1)として報告するのです。

while ($line = $process.StandardOutput.ReadLine()) {
    $result += $line 
}
画面8 画面8 システムロケール「日本語(日本)」では、標準出力の1行目で読み込みがストップしてしまい、結果として1人以上のログオンユーザーを「0」(1-1)と報告していた

 コードを以下のように書き換えると、標準出力を最後まで読み取りますが、「-1」された結果は「4」を返します。

while (($line = $process.StandardOutput.ReadLine()) -ne $null) {
    $result += $line 
}

 日本語環境でquser.exeの標準出力は3行に見えますが、テキストファイルにリダイレクトしてみると、各行の間に空白行があり、全体で5行(-1すると4)になっていました。この余計な空白またはNull行の存在は、他のコマンドラインツールではそうならないものもあるので(例えば、「query session」)、quser.exe(および同等の「query user」)と日本語環境の組み合わせの問題のようです。言語環境の違いを考慮すると、ヘッダで調整するのではなく、quserコマンドの「Active」を含む行をカウントした方がよいでしょう。

 というわけで、Windows Admin Centerの日本語環境における「ログインしているユーザー」の数「0」という誤りは、管理対象のサーバのシステムロケールを「英語(米国)」に変更することで解消できます(画面9)。または、システムロケール「日本語(日本)」のままで、「ベータ:ワールドワイド言語サポートでUnicode UTF-8を使用」オプションを有効化することでも解消できます(画面10)。

画面9 画面9 システムロケール「英語(米国)」では、行数-1で1人以上のログオンユーザーの数を正しく報告
画面10 画面10 システムロケール「英語(米国)」への変更、または「ベータ:ワールドワイド言語サポートでUnicode UTF-8を使用」オプションの有効化で「ログインしているユーザー」問題は解消するが……

 現在のWindowsは、全てのテキスト文字列を内部的にUnicode文字(UTF-16LE)として格納し、処理します。ユーザーやアプリが作成したテキストデータは別です。以前の「メモ帳」(Notepad.exe)の既定では、ANSIコード(日本語はShift-JIS)でテキストを保存していましたが、現在のメモ帳(Windows 10 バージョン1903以降)はUTF-8コードが既定になりました。

 一方、Windowsのシステムロケールが「日本語(日本)」の場合、コマンドプロンプトやWindows PowerShellのコードページは「932(ANSI/OEM - 日本語Shift-JIS)」が既定です。バッチファイルやWSH(Windows Script Host)スクリプト、PowerShellスクリプトに日本語文字列が含まれる場合、ANSIコードで保存しないと文字化けやスクリプトエラーの原因になります。既定のシステムロケールであり、長く利用されてきた「日本語(日本)」を変更してしまうことは、日本語を扱うサーバ側のアプリやツール、スクリプトの実行に、今以上に意図しない影響を及ぼすかもしれません。

 また、「ベータ:ワールドワイド言語サポートでUnicode UTF-8を使用」オプションは、あくまでも「β版」として提供される機能であり、運用環境では使用すべきではありません。

 昔から国際化対応をうたってきたWindowsですが、日本語環境ではちょくちょくこんな問題に遭遇します。Shift-JISとの腐れ縁を断ち切れない限り、こうした問題はこれからも出てくるでしょう。最もシンプルな対応策は「見なかったことにする」「見ないようにする」ことかもしれません。

 最後に、言語環境に依存することなく、ちゃんと動作するはずの「Get-NumberOfLoggedOnUsers」ファンクションを筆者なりに書いてみました。ただし、Windows Admin Centerのバグを修正できるというものではありません(コード署名があるので改変できません)。

 さまざまなPowerShellスクリプトに組み込んで、「(Get-NumberOfLoggedOnUsers).Count」と呼び出せば、ログオン中のユーザーの有無やユーザー数を知ることができます。これを利用すれば、例えば、「タスクスケジューラ」でログオン中のユーザーがいないときにだけ実行するタスクを簡単に実装できるでしょう。動作確認は日本語環境でのみ行っています。

function Get-NumberOfLoggedOnUsers{
$count = 0
$error.Clear();
$process = New-Object System.Diagnostics.Process
$process.StartInfo.FileName = "quser.exe"
$process.StartInfo.UseShellExecute = $false
$process.StartInfo.CreateNoWindow = $true
$process.StartInfo.RedirectStandardOutput = $true 
$process.StartInfo.RedirectStandardError = $true
$process.Start() | Out-Null 
$result = @()
while (($line = $process.StandardOutput.ReadLine()) -ne $null) {
  if($line.Contains("Active")){
    $result += $line 
  }
}
if ([string]::isNullOrEmpty($process.StandardError.ReadToEnd()))
{
    $count = $result.count
}
$process.WaitForExit()
$process.Dispose()
@{Count = $count}
}

筆者紹介

山市 良(やまいち りょう)

岩手県花巻市在住。Microsoft MVP:Cloud and Datacenter Management(2020-2021)。SIer、IT出版社、中堅企業のシステム管理者を経て、フリーのテクニカルライターに。Microsoft製品、テクノロジーを中心に、IT雑誌、Webサイトへの記事の寄稿、ドキュメント作成、事例取材などを手掛ける。個人ブログは『山市良のえぬなんとかわーるど』。近著は『Windows版Docker&Windowsコンテナーテクノロジ入門』(日経BP社)、『ITプロフェッショナル向けWindowsトラブル解決 コマンド&テクニック集』(日経BP社)。


Copyright © ITmedia, Inc. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。