2015年7月8日水曜日

Windows7でリモートデスクトップ part3

前回紹介した方法を更に発展させてみよう。

リモートデスクトップのクライアントには自動で再接続を行うオプションがある。
つまりユーザが意図しないタイミングで切断された場合に文字通り再接続する機能なのだが、この機能と前回紹介した方法はちょっと相性が悪いという弱点があった。

クライアントが再接続したタイミングでサーバ側が切断を検知してセッションを切り替えてしまうのだ。折角繋ぎ直したのに今度はサーバ側からブチンとやられてしまうのである。

そこで、サーバ側が切断を検知してからセッションの切り替えを行うまでに時間的な猶予を儲ける方法を考えた。
つまり、こんな感じの動作である。

  1. サーバがセッションの切断を検知する。 
  2. ウィンドウを表示して、カウントダウンを開始する。
  3. カウントダウン終了までにキャンセルされなければセッションを切り替える。
 カウントダウンには、System32フォルダの中のtimeout.exeを利用する。

それでは前回設定したタスクの内容をタイムアウト対応するよう変更してみよう。 ここに書かなかった項目は前回と同じ設定とする。

  1. コントロールパネルから「管理ツール」を開く。
  2. 管理ツールの中の「タスク スケジューラ」を起動する。
  3. 左側のツリーから「イベントビューアー タスク」を選択する。
  4. 前回追加した「リモートデスクトップ切断後の処理」を右クリックしプロパティ画面を開く。
  5. 「全般」→「ユーザがログオンしている時のみ実行する」にチェックを入れる。
  6. 「操作」→「プログラムの開始」を選択し、編集ボタンを押下する。
  7. 「プログラム/スクリプト」の欄を「cmd」に変更する。
  8. 「引数の追加」の欄を「/q /c timeout 30 && tscon 1 /DEST:console /PASSWORD:*****」に変更して「OK」を押下する(*****は実際のパスワードを入れる)。
これで切断時は最初にカウントダウンが表示され、カウントダウン中に再接続が間に合えば、カウントダウンウィンドウ内でCtrl+Cを押すか、×ボタンでウィンドウ自体を閉じればセッションの切り替えをキャンセルすることが出来るようになった。

2015年6月28日日曜日

androidで辞書のアップデートが終わらない

今月辺りから始まった、androidスマホの通知エリアに「辞書のアップデート情報」が居座って消えてくれない現象があります。
全国のandroidユーザーの端末で一斉に同様の現象が起こり、ググると数万人単位で困っている模様。

これまでに分かっていることを自分なりにまとめるとこんな感じらしいです。
  1. Wifiに繋ぐと通知は消える
  2. ダウンロードマネージャのデータを消去すると消える
参考リンク:Yahoo知恵袋

ひとまず1番の方法で対処したのですが、数日単位で再発して毎回Wifiに繋ぐのも面倒臭い…。

そんな中新しい情報が飛び込んできました。
犯人は「Google日本語入力」らしい!

早速スマホのストアを確認すると「Google日本語入力」アプリに見事に「インストール」の文字が。
あれ、入ってない…。

と言う訳で他に疑わしいのがないか調べてみました。ちなみにandroid4.2.2での操作です。

  1. スマホの設定画面を開く
  2. 「言語と文字入力」を選ぶ
  3. 「キーボードと入力方法」の項目を見る
  4. 「Googleキーボード 英語(米国)」の右側の「>」を選択
  5. 「テキストの修正」を選択
  6. 「アドオン辞書」を選択
 ここまで操作すると辞書の一覧がずらっと出てきます。

ポルトガル語とか、明らかに自分で入れた憶えも使う予定もない辞書がインストール済みになっていたりする辺り、システムが自動的にダウンロードしている気配がぷんぷんします。
私の環境では大体1/4位がダウンロード済みになっていました。

つまりこいつが犯人だとすると、全ての辞書がインストール済みになるまでは何度通知を消してもゾンビのように復活してくる…?

なら手動で全部インストール済みにしてやれば良いじゃない!
と言うことでWifiに繋いで片っ端から辞書をインストールしてみました。

これで暫く様子を見てみます。何週間か経って、二度と通知が出てこなければこいつが犯人だったと言うことですね。

(2015/7/2追記)
また再発しました。
確認してみると、辞書の一覧でアップデートが必要な状態になっている辞書が幾つか。
と言う訳で犯人はこいつで確定ですね。そして手動インストール程度では解決出来ない、と。
はた迷惑すぎる…。

2015年6月3日水曜日

FireFoxでPixivを見るとフリーズする対策

FireFoxでWebを巡回していると、たまに非常に重いサイトがある。
中でもPixivは重い。開くだけでかなりの確率でフリーズする。なんだこれ。

アドオンを見直してみたり、セキュリティソフトを疑ったり、キャッシュや履歴をクリアしてみたり、ハードウェアアクセラレータを切ったり、何をやっても解決しなかったのだが、先日解決方法が分かったので忘れないうちにメモしておこう。

その方法とは、以下の通り。
  1. FireFoxのURL入力欄に「about:config」といれて設定画面を開く。
  2. 検索欄に「network.prefetch-next」と入れる。
  3. 値をfalseにする。
本来はページ内のリンク先のコンテンツを自動的に取得しておいて、そのリンクをユーザが開きたくなった時には既にキャッシュにあるから高速に開けるよ!と言う機能なのだが、これが悪さをしていた模様。

falseに変えたら笑いたくなるほどPixivが軽くなりました。
うん、疑ってゴメンねPixivさん。犯人は君じゃなくてFireFoxだったよ…。

もっとも、設定をfalseに変えることで先読みしなくなるため、場合によってはWebブラウジングの体感速度が遅くなることも予想されるが、フリーズしたりクラッシュするよりはマシなのでこのまま行こうと思う。

2015年4月15日水曜日

拡張メソッドでイベントを定型化する

.NETでは自作イベントを記述する処理は大体決まっていて、コピペの量産祭になることが多い。
そもそもイベントはメモリリークの原因になることが多いのでRxを使え、との議論もあるがここでは置いておいて。

大体こんな感じのコードが随所に溢れることになる。

//----イベントを宣言
public event HogeEventHandler Hoge;
protected virtual void OnHoge(HogeEventArgs e) {
    if(Hoge != null) {
        Hoge(this,  e);
    }
}

//-----イベントを発生
var e = new HogeEventArgs();
OnHoge(e);

何が嫌かというと、イベントの数だけメソッドが増えてしまうことだ。似たようなコードがずらずらと並んでいるのはとても見栄えが悪い。

そこで半分くらいはネタなのだが、以下のような方法を提案してみる。

public static class EventExtension {
    public static void Fire(this EventArgs e, object sender, dynamic handler) {
        if(handler != null) {
            handler(sender, e);
        }
    }
}

dynamic型を利用しているのが格好悪いが、ハンドラを上手い感じに引数に渡す方法がこれしか見付からなかった。もっと上手い指定の方法があれば良いのだが。

これを定義しておくと、以下のように書くことが出来るようになる。

//----イベントを宣言
public event HogeEventHandler Hoge
//----イベントを発生
var e = new HogeEventArgs();
e.Fire(this, Hoge);

同様にしてCancelEventArgsを拡張してみる。

public static class EventExtension {
    public static void Fire(this CancelEventArgs e, object sender, dynamic before, dynamic after, Action action) {
        if(before != null) {
            before(sender, e);
        }
        if(!e.Cancel) {
            action();
            if(after != null) {
                after(sender, e);
            }
        }
    }
}

これを定義しておくと、 以下のように書くことが出来るようになる。

//----イベントを宣言
public event HogeCancelEventHandler BeforeHoge, AfterHoge;
//----イベントを発生
var e = new HogeCancelEventArgs();
e.Fire(this, BeforeHoge, AfterHoge, ()=> Console.WriteLine("Hogeがキャンセルされなかったので実行したよ!"));

如何だろうか。これで個人的にはかなり楽になったのだが、他にやっている人を見掛けないのでバッドノウハウに分類される技なのかも知れない。

PropertyGridのプロパティ名をリソースを使って日本語化する

.NETで設定画面のGUIを作るとき、PropertyGridを使う人は多いだろう。
その際、例えばこんなモデルを設定したとする。

public class AppProperty {
    [Category("基本情報"), Description("名前です。")]
    public string Name { get; set; }
}

すると、画面には「Name」と表示される。
「Name」ならまだ良いが、これがもっと複雑なプロパティ名だった場合は、使いづらくて仕方がない。

解決策としては @IT さんの PropertyGridコントロールに表示されるプロパティ名を変更するには? に自作のAttributeを使う方法が提示されている。非常に有用な方法なのでそのままコピーして再利用されている方も多いだろう。

その方法では、こんな感じにモデルを作ることになる。

[TypeConverter(typeof(PropertyDisplayConverter))]
public class AppProperty {
    [Category("基本情報"), PropertyDisplayName("名前"), Description("名前です。")]
    public string Name { get; set; }
}

大抵の場合は、上記の方法で必要十分だろう。

だが、例えば以下のような場合では上記以外の方法が必要になる。
  • 多言語対応したアプリを作りたい
  •  同じ名前のプロパティが一杯あって、同じ属性の記述を量産するのは何か違う気がする。
こういう用途には、リソースファイルが非常に相性が良い。
つまり、属性からではなくリソースファイルからプロパティ名を取得するようなTypeConverterを自作してやれば良い。

と言う訳で LocalizableConverter なるものを作ってみた。

public class LocalizableConverter<RESOURCES>: TypeConverter {
    public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) {
        return new PropertyDescriptorCollection(
            TypeDescriptor.GetProperties(value, attributes, true)
            .Cast<PropertyDescriptor>()
            .Select(original => new LocalazablePropertyDescriptor(original)).ToArray());
    }
    public override bool GetPropertiesSupported(ITypeDescriptorContext context) {
        return true;
    }
    public class LocalazablePropertyDescriptor: PropertyDescriptor {
        readonly PropertyDescriptor original;
        public LocalazablePropertyDescriptor(PropertyDescriptor original)
            : base(original) {
            this.original = original;
        }
        public override bool CanResetValue(object component) {
            return original.CanResetValue(component);
        }
        public override Type ComponentType {
            get { return original.ComponentType; }
        }
        public override object GetValue(object component) {
            return original.GetValue(component);
        }
        public override string Description {
            get { return original.Description; }
        }
        public override string Category {
            get { return original.Category; }
        }
        public override bool IsReadOnly {
            get { return original.IsReadOnly; }
        }
        public override void ResetValue(object component) {
            original.ResetValue(component);
        }
        public override bool ShouldSerializeValue(object component) {
            return original.ShouldSerializeValue(component);
        }
        public override void SetValue(object component, object value) {
            original.SetValue(component, value);
        }
        public override Type PropertyType {
            get { return original.PropertyType; }
        }
        public override string DisplayName {
            get {
                var name = base.DisplayName;
                var prop = typeof(RESOURCES).GetProperty(name, BindingFlags.Static | BindingFlags.NonPublic);
                if(prop != null) {
                    return (string)prop.GetValue(null);
                }
                return name;
            }
        }
    }
}


ジェネリクスを利用し、型パラメータにResourcesクラスを指定して使う。
リソースにプロパティ名と同名の文字列を定義していればその値が表示され、定義していなければプロパティ名がそのまま表示されるという寸法だ。

使い方としては、こんな感じになる。

[TypeConverter(typeof(LocalizableConverter<Resources>))]
public class AppProperty {
    [Category("基本情報"), Description("名前です。")]
    public string Name { get; set; }
}

あとは、Properties\Resources.resxに「Name」=「名前」になるように文字列を追加してやれば良い。

2014年4月16日水曜日

Windows7でリモートデスクトップ part2

前回紹介した方法では、リモートデスクトップを切断する際にわざわざバッチファイルを叩かなければならなかった。
しかも、管理者権限が必要なので毎回UACの警告が表示される。
ついでに言えば、タイムアウトなどで勝手に切断された場合はバッチファイルが実行されないため自宅のディスプレイは真っ黒のままだ。

本音を言うと、リモートデスクトップが切断されたら自動的にコンソールセッションがアクティブになって欲しい。切断するたびに特別な操作なんてやりたくないのだ。

とは言え、これ以上の方法を探しても意外と見付からない。
gpedit.mscなどで探しても、リモートデスクトップ接続時に自動的にプログラムを走らせる設定はあるが逆はない。

なので、これから紹介する方法は本ブログオリジナルの方法である。


まず最初に、リモートデスクトップの切断を検知する方法から調べてみよう。

コントロールパネルの管理者ツールからイベントビューアを起動する。
Windows7のイベントビューアはごちゃごちゃしていて見づらいが仕方がない。

アプリケーションとサービスログ > Microsoft > Windows >  TerminalServices-LocalSessionManager > Operational

と、ツリーを展開していく。ここがリモートデスクトップのログの場所だ。既に何度かリモートデスクトップを利用しているなら再接続に成功したり、切断されたり、色々な種類のログが確認できるはずだ。

では、ここから切断時のログだけをピンポイントに抜き出してみよう。

カスタムビューと書かれたラベルを右クリックし、「カスタムビューの作成」を選択する。
「ログごと」にチェックが入っていることを確認し、「イベントログ」の右のコンボボックスの▼を押して
アプリケーションとサービスログ > Microsoft > Windows >  TerminalServices-LocalSessionManager > Operational
を選択する。

ここまでは比較的簡単。大変なのはここからである。

「フィルター」のタブを「XML」に切り替える。
「手動でクエリを編集する」にチェックを入れる。確認が表示されるが「はい」を選ぶ。

<Select Path="Microsoft-Windows-TerminalServices-LocalSessionManager/Operational">*</Select>

となっている部分を

<Select Path="Microsoft-Windows-TerminalServices-LocalSessionManager/Operational">*[System[EventID=24 and Level=4] and UserData/EventXML/Address!='ローカル']</Select>

と書き換える。これで、ローカル「以外」から切断した場合だけを取得するようにXPath形式で指定したことになる。

「OK」を押し、フィルタに「リモートデスクトップ切断」とでも名前を付けて更に「OK」を押す。

これで屋外からリモートデスクトップで切断した時のログだけを抽出することが出来るようになったはずだ。


次に、「リモートデスクトップ切断」にログが追加される度にプログラムを起動する方法について説明する。

先程保存した「リモートデスクトップ切断」を右クリックし、「このカスタムビューにタスクを設定」を選択する。
「基本タスクの作成」で「リモートデスクトップ切断後の処理」とでも名前を付けて「次へ」をクリック。
「イベントログへの記録時」でカスタムイベントフィルターと表示されていることを確認して「次へ」をクリック。
「操作」で「プログラムの開始」を選択して「次へ」をクリック。
「プログラム/スクリプト」に「tscon」、「引数の追加」に「1 /DEST:console /PASSWORD:*****」と入力して「次へ」をクリック(*****は実際のパスワードを入れる)。
「[完了]をクリックしたときに、このタスクの[プロパティ]ダイアログを開く」にチェックを入れて「完了」をクリック。

まだまだ続く。次はプロパティダイアログにて。

「全般」タブの中で、「ユーザがログオンしているかどうかにかかわらず実行する」にチェックを入れる。 念のため「パスワードを保存しない」のチェックは外しておこう。そして「最上位の特権で実行する」にチェックを入れる。
「条件」タブの中で、「タスクを実行するためにスリープを解除する」にチェックを入れる。
「設定」タブの中で、「タスクを要求時に実行する」だけON、他のチェックは全てOFFにする。
「OK」を押してダイアログを閉じる。パスワードを聞かれた場合は入力する。

これで「リモートデスクトップ切断時に自動的にコンソールセッションをアクティブにする」ことが出来るようになった。もう×ボタンで閉じようが、タイムアウトで勝手に切断されようが関係ない。

作成したタスクはコントロールパネルの「管理ツール」→「タスクスケジューラ」の「イベントビューアータスク」で確認することが出来る。

以上!

2015/7/13
続きの記事を書きました。

Windows7でリモートデスクトップ part1

たまに屋外から自宅のPCを操作したくなるときがある。
そんなときに、リモートデスクトップは最もお手軽な手段の一つであろう。

自宅と同じ環境で操作したいため、屋外からは以下のコマンドでリモートデスクトップに接続することにしている。

mstsc /admin

ただ、リモートデスクトップを切断して自宅に帰ってみると、ディスプレイが真っ黒のまま何の操作も受け付けなくなっていて途方に暮れることがある。

これはコンソールセッションがアクティブになっていないことが原因である。

試しに、自宅のPCでコマンドプロンプトを起動して以下のコマンドを打ってみよう。

query session

すると、こんな感じになるのではないだろうか。


セッション名ユーザ名ID状態種類デバイス
services0Disc
>consoleHoge1Active


コンソールセッションがアクティブになっているのが分かる。

次に、屋外からリモートデスクトップで接続して同じコマンドを打ってみよう。
こんな感じになるはずだ。


セッション名ユーザ名ID状態種類デバイス
services0Disc
>rdp-tcp#0Hoge1Activerdpwd
console13Conn
rdp-tcp65536Listen


コンソールセッションとは別のセッションがアクティブになっているのが分かる。

では、どうすれば良いか。切断時にコンソールセッションがアクティブになるようにすれば良い。

×ボタンでリモートデスクトップを閉じたり、スタートメニューからログオフや切断を選ぶ代わりに、コマンドプロンプトを管理者権限で起動し、以下のコマンドを実行する。

tscon 1 /DEST:console /PASSWORD:*****

現在アクティブなIDの1番をコンソールに接続するよ、と言う意味だ。そうすると自動的にリモートデスクトップは切断され、自宅のPCが操作可能な状態に戻る。
*****には実際のパスワードを入れよう。XPの時代は指定しなくても上手く行ったのだが、7になってからはこのオプションを指定しないと上手く行かなかった。

毎回管理者権限でコマンドプロンプトを起動してコマンドを叩くのは面倒なので、バッチファイルを作成し、そのバッチファイルのショートカットを作成し、そのショートカットのプロパティ画面の詳細設定から「管理者として実行」を有効にしておく。
あとはそのショートカットをスタートメニューなりに登録し、リモートデスクトップを切断する際にそのショートカットを実行するようにすれば良い。

実はもっと上手くやる方法があるのだが、長くなったのでその説明は次回に回そう。