TEMTECOMAI ORTHOSTATIC HYPOTENSION

元ダメプログラマで現ダメ中間管理職の駄文

Windows 10 の Inside Preview でローンチしたのは 7月の半ばでしたかね。
そして翌月の月例アップデートに MS15-091 : Microsoft Edge 用の累積的なセキュリティ更新プログラム (3084525) が 「緊急」 として初登場。
インパクトのある初登場でした。
9月には MS15-095 : Microsoft Edge 用の累積的なセキュリティ更新プログラム (3089665) が 「緊急」 として 2ヶ月連続 2回目の登場。
10月には MS15-107 : Microsoft Edge 用の累積的なセキュリティ更新プログラム (3096448) が若干ランクを下げた 「重要」 で 3ヶ月連続 3回目の登場。
11月は MS15-113 : Microsoft Edge 用の累積的なセキュリティ更新プログラム (3104519) で再び 「緊急」 に返り咲き、4ヶ月連続 4回目の登場。
12月ももちろん MS15-125 : Microsoft Edge 用の累積的なセキュリティ更新プログラム (3116184) を 「緊急」 でぶっこみ、見事 5ヶ月連続 5回目の登場を果たした。

頑張ってください。

たとえテストだろうがインストール要件に 4コアの CPU とか書いてあるわけだからそのとうりのスペックを用意せにゃならんのだろうね。
4年前に導入した Core i-3 2100 3.1GHz だと 2Core 2HT でギリギリなわけだけど、その全てを cCPU としてつぎ込んじゃっても大丈夫なのかね。
今時の仮装環境って CPU の数がオーバーしてても大丈夫なの?
同時稼働してる仮想マシン群の vCPU の合計が物理 CPU の数をオーバーしても大丈夫、って記事があるのだからきっと大丈夫だよね

一人当たり月額 1500円くらい高いけど Skype for Business (旧 Lync) ですんげー人数が参加する会議 (経営トップから全従業員への新年の挨拶とか) ができたり、(日本ではまだ開始時期が未定だけど) Skype For Business から通常の電話にかけられるようになるんだってさ。
他にもいろいろ機能がアップしてるんだってさ。
1ライセンスだけで E3 を契約してるボクには関係ない話だね。

年明けて時間があれば試してみようと思う。
てか自分で契約してる SharePoint Online 使えよとか思うけど、無償版なりに何ができるのかわからんので。
まぁ今時社内静的な HTML でシコシコ編集して社内ポータル後悔してるのもなんだかなだし、Expression Web に怒られっぱなしなソースだし、本職の人が見たら卒倒しちゃうようなソースだし、何の計画性もなく積み上げられた CSS だし。
このまま行くといつかは本文無しで単なる PDF ファイルへのリンク集になっちゃいそうだし。
オンプレミスで SharePoint Server を入れるほどの費用対効果もなさそうだしね。

一時期随分と悩んでいた件、sem さんからコメントにてお教えいただいたように、DB 側の remote query timeout の値を初期値の600 から無制限の 0 に変更したところ無事に完了するようになりました。
感謝です。
USE SUSDB ;
GO
EXEC sp_configure 'remote query timeout', 0 ;
GO
RECONFIGURE ;
GO

VMware で作成した Windows Server で Active DIrectory を構築しようとした。
ホストの物理ネットワークへは接続しないよう、独立した仮想ネットワークを設定。
VMnet2 をカスタマイズ。 (といってもローカル DHCP サーバーを無効にしただけ)
Windows Server をインストールし、決めておいた IP アドレスに設定。
dcpromo.exe のウィザードで Active Directory をインストール。
ウィザードの途中で 「IP アドレスが動的になってるけど、このまま動的で行っちゃう? yse / no」 とのダイアログが出たので迷わず [no] をクリックしたが、再び同じダイアログが表示されて前に進めない。
ipconfig /all してみると確かに固定アドレスも設定してあるが、自動構成でローカル アドレスが "(優先)" で振られてしまう。 ipconfig /all してみると確かに自動構成で 169.254. のローカル アドレスが付与されていて (優先) となっている。 自分で設定したアドレスのほうは末尾に (重複) と表示されている。

VMnet2 の設定を見直したところ 「ホスト オンリー」 になっていた。 つまり仮想マシンはホスト マシンとだけ通信が可能な設定。
ホスト マシンで ipconfig /all してみたら VMnet2 のアダプターにアドレスが振ってあり、そのアドレスが仮想マシンと被っていた。 ホスト マシン側の VMnet2 アダプターに明示的にアドレスを設定したつもりがなかったので気づかなかったよ。

ここ数日の成果物は PowerShell のスクリプト ファイル (*.ps1) と SQL Serve 用のスクリプト ファイル (*.sql) に分散してしまっている。
どうせなら PowerShell に一本化したいよね。
残念ながら Windows Server 2008 R2、WSUS 3.0、Windows Internal Database という我が WSUS 環境では PowerShell による管理が整備されきっていない時代の物らしく、WSUS はアセンブリを読み込んで .NET 的なアプローチをし、SQL Server は sqlcmd.exe に sql ファイルを流し込む事になるようです。
しかも sqlcmd.exe は Windows Internal Database ではインストールされないようなので別途無償版の SQL Server Management Studio Express とかをインストールしないとなりませんね。
Windows Server 2012 の WSUS 4.0 では PowerShell のコマンドレットが用意されているみたいですね。
Windows Internal Database はどうなんでしょうか。。。 SQL Server 2012 以降なら PowerShell 用の sqlps モジュールもありますが、WID ではダメなんでしょうか。。。 ダメかもわからんね。。。

スクリプト センターにある Re-index the WSUS 3.0 Database っていう SQL はデータベースのインデックスを再構成するためのメンテナンス スクリプトだけれど、こいつを流すと OS の表示が Windows 10 から Vista に戻っちゃうよ。
tbComputerTargetDetail テーブルの OSDescription 列の内容が戻っちゃうんだよね。
なので先日のスクリプトと合体させてみた。
USE SUSDB; 
GO 
SET NOCOUNT ON;

-- Rebuild or reorganize indexes based on their fragmentation levels 
DECLARE @work_to_do TABLE ( 
    objectid int 
    , indexid int 
    , pagedensity float 
    , fragmentation float 
    , numrows int 
) 

DECLARE @objectid int; 
DECLARE @indexid int; 
DECLARE @schemaname nvarchar(130);  
DECLARE @objectname nvarchar(130);  
DECLARE @indexname nvarchar(130);  
DECLARE @numrows int 
DECLARE @density float; 
DECLARE @fragmentation float; 
DECLARE @command nvarchar(4000);  
DECLARE @fillfactorset bit 
DECLARE @numpages int 
 
-- Select indexes that need to be defragmented based on the following 
-- * Page density is low 
-- * External fragmentation is high in relation to index size 
PRINT 'Estimating fragmentation: Begin. ' + convert(nvarchar, getdate(), 121)  
INSERT @work_to_do 
SELECT 
    f.object_id 
    , index_id 
    , avg_page_space_used_in_percent 
    , avg_fragmentation_in_percent 
    , record_count 
FROM  
    sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL , NULL, 'SAMPLED') AS f 
WHERE 
    (f.avg_page_space_used_in_percent   < 85.0 and f.avg_page_space_used_in_percent/100.0 * page_count   < page_count - 1) 
    or (f.page_count > 50 and f.avg_fragmentation_in_percent > 15.0) 
    or (f.page_count > 10 and f.avg_fragmentation_in_percent > 80.0) 

PRINT 'Number of indexes to rebuild: ' + cast(@@ROWCOUNT as nvarchar(20)) 

PRINT 'Estimating fragmentation: End. ' + convert(nvarchar, getdate(), 121) 

SELECT @numpages = sum(ps.used_page_count) 
FROM 
    @work_to_do AS fi 
    INNER JOIN sys.indexes AS i ON fi.objectid = i.object_id and fi.indexid = i.index_id 
    INNER JOIN sys.dm_db_partition_stats AS ps on i.object_id = ps.object_id and i.index_id = ps.index_id 

-- Declare the cursor for the list of indexes to be processed. 
DECLARE curIndexes CURSOR FOR SELECT * FROM @work_to_do 

-- Open the cursor. 
OPEN curIndexes 

-- Loop through the indexes 
WHILE (1=1) 
BEGIN 
    FETCH NEXT FROM curIndexes 
    INTO @objectid, @indexid, @density, @fragmentation, @numrows; 
    IF @@FETCH_STATUS   < 0 BREAK; 

    SELECT  
        @objectname = QUOTENAME(o.name) 
        , @schemaname = QUOTENAME(s.name) 
    FROM  
        sys.objects AS o 
        INNER JOIN sys.schemas as s ON s.schema_id = o.schema_id 
    WHERE  
        o.object_id = @objectid; 

    SELECT  
        @indexname = QUOTENAME(name) 
        , @fillfactorset = CASE fill_factor WHEN 0 THEN 0 ELSE 1 END 
    FROM  
        sys.indexes 
    WHERE 
        object_id = @objectid AND index_id = @indexid; 

    IF ((@density BETWEEN 75.0 AND 85.0) AND @fillfactorset = 1) OR (@fragmentation   < 30.0) 
        SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REORGANIZE'; 
    ELSE IF @numrows >= 5000 AND @fillfactorset = 0 
        SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REBUILD WITH (FILLFACTOR = 90)'; 
    ELSE 
        SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REBUILD'; 
    PRINT convert(nvarchar, getdate(), 121) + N' Executing: ' + @command; 
    EXEC (@command); 
    PRINT convert(nvarchar, getdate(), 121) + N' Done.'; 
END 

-- Close and deallocate the cursor. 
CLOSE curIndexes; 
DEALLOCATE curIndexes; 

IF EXISTS (SELECT * FROM @work_to_do) 
BEGIN 
    PRINT 'Estimated number of pages in fragmented indexes: ' + cast(@numpages as nvarchar(20)) 
    SELECT @numpages = @numpages - sum(ps.used_page_count) 
    FROM 
        @work_to_do AS fi 
        INNER JOIN sys.indexes AS i ON fi.objectid = i.object_id and fi.indexid = i.index_id 
        INNER JOIN sys.dm_db_partition_stats AS ps on i.object_id = ps.object_id and i.index_id = ps.index_id 
    PRINT 'Estimated number of pages freed: ' + cast(@numpages as nvarchar(20)) 
END 
GO 

-- 不具合対応 ここから

-- Windows 8.1 の PC 情報を修正
UPDATE [SUSDB].[dbo].[tbComputerTargetDetail]
SET [OSDescription] = 'Windows 8.1'
WHERE [OSMajorVersion] = '6'
AND [OSMinorVersion] = '3'
AND [OldProductType] = '1'
AND ([OSDescription]   <> 'Windows 8.1' or [OSDescription] IS NULL) 

-- Windows Server 2012 R2 の PC 情報を修正
UPDATE [SUSDB].[dbo].[tbComputerTargetDetail]
SET [OSDescription] = 'Windows Server 2012 R2'
WHERE [OSMajorVersion] = '6'
AND [OSMinorVersion] = '3'
AND [OldProductType]   <> '1'
AND ([OSDescription]   <> 'Windows Server 2012 R2' or [OSDescription] IS NULL) 

-- Windows 10 の PC 情報を修正
UPDATE [SUSDB].[dbo].[tbComputerTargetDetail] 
SET [OSDescription] = 'Windows 10' 
WHERE [OSMajorVersion] = '10' 
AND [OSMinorVersion] = '0' 
AND [OldProductType] = '1' 
AND ([OSDescription]   <> 'Windows 10' or [OSDescription] IS NULL)
-- 不具合対応 ここまで --


--Update all statistics 
PRINT 'Updating all statistics.' + convert(nvarchar, getdate(), 121)  
EXEC sp_updatestats 
PRINT 'Done updating statistics.' + convert(nvarchar, getdate(), 121)  
GO 
まぁまぁいいんじゃない?

まぁね、WSUS がため込んでるデータ量が多すぎて処理が続行不可になってるって話じゃないと思ってますよ。
そこはまだ楽観的です。
新しい更新プログラムも取りに行けているしクライアントも Windows Update の処理ができていますし。

とは言え広大なネットの海にダイブして下記の情報を引っかけました。
その割にはどちらも Technet のフォーラムですが。。。

Getting past WSUS Cleanup Wizard time out, removing unnecessary updates.

なんかね、WSUS 3.0 が使っているデータベースが持っているストアド プロシージャーに spGetObsoleteUpdatesToCleanup ってのがあるらしいんですよ。
Get Obsolete Updates って言うぐらいだから古い更新情報を取得するってことだよね。
で、こいつはヒットした更新情報の ID 一覧を出力する。
そして spDeleteUpdate っていうストアド プロシージャの引数 @localUpdateID にヒットした ID を与えてあげると更新情報のレコードを削除するよっていう話らしい。

ただ spGetObsoleteUpdatesToCleanup で尋常じゃない数の更新情報が出力されるので、結果をいったん一時テーブルに登録して、WHILE を使って一つずつ spDeleteUpdate に渡してやろうっていうのが次のリンク。

WSUS Cleanup - can this be done is phases?

ザックリとまとめるとこんな感じ。
use SUSDB;
GO

DECLARE @var1 INT
DECLARE @msg nvarchar(100)

-- spGetObsoleteUpdatesToCleanup の結果を一時テーブル #results に格納
CREATE TABLE #results(Col1 INT)
INSERT INTO #results(Col1) EXEC spGetObsoleteUpdatesToCleanup

DECLARE WC Cursor FOR
    SELECT Col1 FROM #results
	
OPEN WC
FETCH NEXT FROM WC
INTO @var1

WHILE(@@FETCH_STATUS > -1)
BEGIN
    SET @msg = 'Deleting ' + CONVERT(varchar(10), @var1)
    RAISERROR(@msg, 0, 1) WITH NOWAIT EXEC spDeleteUpdate @localUpdateID=@var1
    FETCH NEXT FROM WC
    INTO @var1
END

CLOSE WC

DEALLOCATE WC

DROP TABLE #results

これを sql ファイルとして保存して Management Studio とかで流してあげればいいよね。

それでどれだけ効果があるのかわからないけれど。

-- 追記です --

この件、解決しましたのでリンクしておきます。

WSUS 3.0 SP2 on Windows Server 2008 R2 (Windows Internal Database 使用)

WSUS のクリーンアップは時間がかかる。
ホントにかかる。
数日待たされる可能性もある。

その間ずっと管理者でログオンして管理ツールを開いていることになる。
リモート デスクトップから操作してたら作業用 PC の電源も切れなくなってしまうし、だからと言ってサーバーにログオンしっぱなしってのもよろしくない。
ならばタスクで自動実行すればいいんじゃないだろうか、と。


とか思っていたらマイクロソフトのスクリプト センターにありましたよ。 さすが Scriptyng Guys だ。
WSUS Cleanup
https://gallery.technet.microsoft.com/ScriptCenter/fd39c7d4-05bb-4c2d-8a99-f92ca8d08218/

[reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration")` 
 | out-null 
$wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer(); 
$cleanupScope = new-object Microsoft.UpdateServices.Administration.CleanupScope; 
$cleanupScope.DeclineSupersededUpdates = $true        
$cleanupScope.DeclineExpiredUpdates         = $true 
$cleanupScope.CleanupObsoleteUpdates     = $true 
$cleanupScope.CompressUpdates                  = $true 
#$cleanupScope.CleanupObsoleteComputers = $true 
$cleanupScope.CleanupUnneededContentFiles = $true 
$cleanupManager = $wsus.GetCleanupManager(); 
$cleanupManager.PerformCleanup($cleanupScope);
こいつを ps1 ファイルにしてタスク スケジューラーに登録しろ、という話ですかね。
スクリプトをパッと見てすぐにわかりますが、[Refrection.assembly]::LoadWithPartialName() で Microsoft.UpdateServices.Administration を読み込んでいるので、PowerShell らしくない方法ですよね。
Windows Server 2012 の WSUS は PowerShell 用のモジュールがあるらしいのですが、WSUS 3.0 には存在しない。 だからアセンブリを読み込んで処理を行う、ということでしょう。

スクリプトのほとんどの部分はクリーンアップ ウィザードの UI にあるチェックボックスの項目っぽいですね。 これらのプロパティに $true または $false を設定して処理項目を決定するわけですね。
ちょっと UI に表示されている処理項目と数が合わないけれど、まぁクリーンアップの範囲でしょう。
CleanupObsoleteComputers がコメントアウトされてます。 しばらく (30日だっけ?) アクセスしていないコンピューターの情報を削除するって処理でしょうか。

タスクで無人実行するのだから try-catch を使って、エラーもイベントログに出力するようにしたらいいんじゃないかね。

様々なバージョンの IE や Edge (Windows 10) での動作確認をするための仮想環境をマイクロソフトが提供してくれていることを知った。
https://dev.windows.com/ja-jp/microsoft-edge/tools/vms/windows/

2015年11月12日現在は以下の環境が用意されている。

テストの対象環境として準備されている仮想マシン

  • IE6 on XP
  • IE7 on Vista
  • IE8 on XP
  • IE8 on Win7
  • IE9 on WIn7
  • IE10 on Win7
  • IE10 on Win8
  • IE10 on WIn8.1
  • IE11 on Win7
  • MSEgde on Win10
仮想環境をホストするのが Windows の場合は以下のプラットフォームが選択可能。
  • VirtualBox
  • Vagrant
  • HyperV
  • VPC (対象の仮想マシンが Windows XP, Vista, 7 の場合のみ)
  • VMware
ホストが Mac の場合は VirtualBox、Vagrant、VMware、Parallels が選択でき、Linux の場合は VirtualBox と Vagrant が選択可能。

このページの日本語翻訳が恥ずかしいことになっている。
入力ミスか
機械語翻訳による誤訳ではなく、完全にヒューマン エラーだね。

WSUS 3.0 SP2 on Windows Server 2008 R2 (Windows Internal Database 使用)

WSUS のメンテナンスをしようと思った。
WSUS 3.0 には [クリーンナップ ウィザード] という GUI ツールがある。
実行してみた。
終わらない。
翌朝まで放置した。
データベース エラーでタイムアウトしていた。

いろいろ調べて回った。
メンテナンス自体をしばらくサボっていたため、日常メンテナンスに関する情報とタイムアウトに対する対策情報がゴチャゴチャとなってしまった。

まずはサボっていた日常メンテナンスから始めてみる。

WSUS 管理コンソールを起動する。
すでに存在しない古い Windows に関する更新情報を除外する。
[Update Services]-[サーバー]-[Update[オプション]-[製品とクラス] を開き、使っていない古い Windows を対象から外した。

IE の古い累積的なセキュリティ更新情報を "拒否" にする。
Japan WSUS Support Team blog "「IE の累積的なセキュリティ更新プログラム」が承認されていると、更新プログラムの検出処理時に WSUS クライアントの CPU 使用率が高くなる"
ただしこの検索結果一覧は使い勝手が悪すぎる。 検索結果のリストからは親子関係が分からない。 せめて親子関係を表すアイコンも表示されてほしかった。


これで再びクリーンアップ ウィザードを実行してみた。
やっぱりタイムアウトする。

では DB のインデックスを再構築してみよう。 WSUS と言えど情報は DB のレコードなのだから、肥大化した DB を整理してやれば軽くなるはず!!

Japan WSUS Support Team blog 「WSUS DB インデックスの再構成の手順について」
こいつも SQL なので Management Studio から実行。
あまり時間は掛からなかった。


クリーンアップ ウィザードを試してみた。
やっぱりタイムアウトする。

他にやれる事はないだろうか。
SQL で DB に直接介入するってのはどうだ?
WSUS DB にはメンテナンス用のストアド プロシージャーが用意されているのではないだろうか。
PowerShell で Update Service に介入するってのはどうだ?
GUI が CUI になっただけでかもしれないが管理コンソールで発生するタイムアウトは回避できるかもしれない。

まだチャレンジは続きそうだ。

-- 追記です --

この件、解決しましたのでリンクしておきます。

WSUS 3.0 SP2 on Windows Server 2008 R2 (Windows Internal Database 使用)

管理ツールの [Windows Server Update Services] でコンピューターの一覧を見てみると Windows 10 が Vista として表示されてしまう。 (Windows 8.1 も誤表示されていたらしいが WSUS のメンテをサポっていたので気づかなかった)

Microsoft からは WSUS 3.0 の不具合を修正する Hotfix がいくつか出ているようだが、どの Hotfix でも現象は治らない。
ちなみにこの環境にすでに適用してある Hotfix は以下の 4つ。


このうち 4つ目の KB2938066 の HotFix では、Windows Server 2012 R2、Windows 8.1、Windows Storage Server 2012 の表示名を修正するらしいのだが、こちらの環境では Windows 10 の誤表示は治っていない。
Japan WSUS Support Team Blog 「WSUS コンソール画面でオペレーティング システム名が正しく表示されない事象について」


そこで見つけたのが以下の情報
Windows Server Essentials Tips & Tricks 「Windows 10 on WSUS Shows as Windows Vista」

WSUS が使っている SUSDB というデータベースの中のテーブル [tbComputerTargetDetail] の中にはその環境で WSUS に登録されたコンピューターの情報が入っているので、こいつを修正するって感じか?
てっきり Windows OS に関するマスター テーブルのようなものがあってそいつを修正するのかと思ったが、現物の情報を修正するようだ。
UPDATE [SUSDB].[dbo].[tbComputerTargetDetail]
SET [OSDescription] = 'Windows 10'
WHERE [OSMajorVersion] = '10'
AND [OSMinorVersion] = '0'
AND [OldProductType] = '1'
AND ([OSDescription] <> 'Windows 10' or [OSDescription] IS NULL)


これを以下のようにして修正した。
上記のコードは SQL なので、こいつを実行する必要がある。
コマンドラインで実行すればいいのだけれど、いろいろと探索したいところなので SQL Server Management Studio を使う。
Windows Internal Database はリモートからログオンできないらしいのでサーバー自身にインストールを行う必要があるらしい。
この WSUS のサーバーには SQL Server 2008 R2 Management Studio Express をインストールした。

  1. SQL Server へログオンする。

サーバーの種類 : データベース エンジン
サーバー名 : \\.\pipe\mssql$microsoft##ssee\sql\query
認証 : Windows 認証
  1. [新しいクエリ] で先のコードを貼り付け実行する。


せっかくなので先のブログにあるように Windows 8.1 と Windows Server 2012 R2 に関する修正も入れる。
無事に終了。

最近 PowerShell のお勉強に力を入れています。
対象は主に Active Directory の管理。

それとは別に、Office 365 管理の文書を読んでいると Exchange の管理なんかにも PowerShell がバンバン搭乗してくる。
たしか Windows Server 2008 あたりから標準搭載されたんだっけ?
随分と不勉強な期間を過ごしてしまったものだ。。。

とりあえず Windows 8.1 の Active Directory 管理センターが生成するスクリプトを見て勉強中。
ユーザー アカウントを新規作成するだけでも 4つぐらいのスクリプトを流しているみたい。
  1. 基本的な項目を指定してアカウントを作成する。
  2. セキュリティ関連の設定をする。
  3. アカウントを有効にする。
  4. 次回ログオン時にパスワード変更を強制するようにする。

みたいな。
最初の New-ADUser コマンドレットにもう少し作業を集約できやしないかね。

あとは作成したスクリプトの中に値を指定していく方法を考えなくちゃね。
ユーザーを作成するケースは 3種類程度で、ケースごとに指定する項目が若干増減する。
ユーザーの情報を変更するケースも何種類かに限られる。
所属が変わる。
社内権限が変わる。
PC へのログオンを禁止する。 またはその逆。
メール アドレスが変わる。 (外部の POP3 なので連動しない)
削除のケースは一つだけ。 単純に削除するだけ。

この作業をするためのツールを自作するにしても、汎用的な作業に使えるようなものになってしまうと車輪の再発名になってしまうので、上に挙げたような実際に起こるケースを起点にすればいいのかな。

何らかの UI を作ったとして、入力したものを最終的に PowerShell に流すにはどうしたらいいのかな。
パイプを駆使すれば 1行のコマンドで済むだろう。それなら Shell で PowerShell を起動してコマンドを引数で与えれば大丈夫かもしれない。
コマンドが複数行になってしまう場合はどうだろう。 スクリプトファイルを生成してから渡せばいいのかな。

というか UI の言語から直接 PowerShell をコントロールすることはできるのかな。
UI は何で作ればいいんだろうか。
Windows フォーム? 今更だなぁ。。。
今の主流派 WPF なのかな?
Windows フォームの開発者たちは既に WPF を習得しちゃったのかな。 MVVM とか書籍を読んでも概念ばかりでよくわからないよ。

↑このページのトップヘ