「ほっ」と。キャンペーン
<   2010年 04月 ( 18 )   > この月の画像一覧
(.Net,ADSI)OUを取得する際のLDAP条件
ActiveDirectoryの情報を参考にする方法については、(.Net,ADSI)Active Directroyの情報を参照。を参考にしてください。

OUの一覧を取得するLDAP条件は下記でできるようです。(C#)

// LDAP検索オブジェクトを作成
DirectorySearcher drSearch = new DirectorySearcher(mDrctEntry);
// アカウントフィルターを設定 Userオブジェクトだけ取得するように
drSearch.Filter = "(ObjectCategory=organizationalUnit)";
 

//ちなみにユーザオブジェクトを取得する時のLDAPクエリは下記
//drSearch.Filter = "(ObjectCategory=user)";


ただ、親子関係は考慮してないので、階層として表現するのは難しいかもしれません。
ただ単にOUが列挙されるだけみたいです。

階層も含めて取得するいい方法ありませんかね。。。。
[PR]
by jehoshaphat | 2010-04-29 23:14 | .Net開発 | Trackback | Comments(0)
(.Net)カスタムクラスをDataGridViewにバインドしたときに、選択したレコードのオブジェクトを取得する
List<T>を DataGridView にバインドしてるんですが、ユーザーが行を選択した時に選択された行に対応するオブジェクト取得する方法です。
.Net にはそのためのプロパティが用意されていました。
それは、DataGridViewRow.DataBoundItemプロパティです。
詳しくはMSDN:方法 : Windows フォームの DataGridView 行にバインドされたオブジェクトにアクセスするが参考になります。

ただ、行選択モード(DataGridViewSelectionMode)が FullRowSelect にしといたほうが安全ですね。
セルだけ選択した場合は取得できないようなので。。。

とりあえず、サンプルです。(C#)

public partial class Form1 : Form{
List<user> mlst = new List<user>();
 
private void Form1_Load(object sender, EventArgs e) {
dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
mlst.Add(new user("hogehoge1", "1"));
mlst.Add(new user("hogehoge2", "2"));
mlst.Add(new user("hogehoge3", "3"));
 
dataGridView1.DataSource = null;
dataGridView1.DataSource = mlst;
}
 
private void button3_Click(object sender, EventArgs e) {
foreach (DataGridViewRow row in dataGridView1.SelectedRows) {
//選択したレコードに対応するオブジェクトを取得
user u =(user)row.DataBoundItem;
Console.WriteLine(u.name + " , " + u.id);
}
Console.WriteLine();
}
}
 
//カスタムクラス(エンティティ)
public class user{
public string name { get; set; }
public string id { get; set; }
public user(string sname, string sid) {
name = sname;
id = sid;
}
}

[PR]
by jehoshaphat | 2010-04-29 23:08 | .Net開発 | Trackback | Comments(0)
(.Net)サブスレッドからメインスレッドのFormを操作したい
マルチスレッドのプログラムで、重たい処理を行うサブスレッドから、フォームを操作する方法です。
調査するとフォームのコントロールはメインスレッドで動くようです。
なので、サブスレッドからそのままフォーム上のコントロールを触ると、例外が発生します。

これを回避するには下記のように、コントロールの Invoke メソッドを使ってアクセスするといいようです。(C#)

private void Form1_Load(object sender, EventArgs e)
{
mTh = new Thread(new ThreadStart(ThreadMethod));
mTh.IsBackground = true;
mTh.Start();
}
 
//別スレッドで動くメソッド
private void ThreadMethod()
{
//親元のスレッドで動くフォームを操作
this.Invoke(
(MethodInvoker)delegate()
{
this.Label1.Text = "hogehoge";
});
}



参考:
別スレッドからフォームコントロールを操作するには?
@IT:.NET TIPS Windowsフォームで別スレッドからコントロールを操作するには?
C#と諸々 Windowsアプリケーションにおけるマルチスレッドの注意点 もう少し深い内容まで触れられています。


補足:
上記では、Control.Invoke メソッドを使いましたが、Control.BeginInvoke メソッドを使う方法もあるようです。

違いは、Control.Invoke メソッドが同期呼び出しになり、Control.BeginInvoke メソッドが非同期呼び出しになるようです。
どっちを使っても構わないようですが、非同期で処理する必要がないのであれば、Invokeの方が良いという意見があります。

参考:
MSDN:方法 : スレッドからコントロールを操作する
#54 Control.BeginInvokeは使ってはいけない « Visual Basic « Gab_kmのブログ
Part 1. Windows フォームのマルチスレッド処理の基礎 - とあるコンサルタントのつぶやき - Site Home - MSDN Blogs
[PR]
by jehoshaphat | 2010-04-29 23:04 | .Net開発 | Trackback | Comments(0)
Windows7 x64 での LAME インストールとx264vfw

Core i5 にグレードアップしたので貯まってる録画ファイル(MPEG2)を H.264 + mp3 に変換しようと思ってます。
フロントエンドの変換ツールは使い慣れた VirtualDubMod を使います。

で、音声圧縮のところでmp3を出すべく LAME のインストールをします。
RAREWARESから、LAME ACM codec をダウンロードします。

ZIPを解凍後、LameACM.inf をインストールするんですが、x64の場合は「HOWTO Install LAME-ACM on x64.txt」に従ってインストールしないといけないようです。
下記の手順でインストールできます。
1.コマンドプロンプトを管理者権限で実行。
2.下記コマンドを実行

>cd %windir%\SysWOW64
>rundll32 setupapi.dll,InstallHinfSection DefaultInstall 0 解凍後のパス\LameACM.inf



あと、H264 エンコーダですが、x264がVFWに対応しなくなったような記憶があったんでffdshowでやってたんですが、Dvix6.9にも劣る画質でorz
で、よく調べるとx264の派生かもしれませんが、VFW(Video for Windows)板もあるんですね。
下記からダウンロードできました。
http://sourceforge.net/projects/x264vfw/files/
VirtualDubから使うには、x64版ではなく、x86版をいれないといけないようです。
[PR]
by jehoshaphat | 2010-04-27 23:11 | 豆知識 | Trackback | Comments(0)
VBAのLine Input で改行コード(LF)が認識されない

あるC#アプリで、CSVファイル出力時に"\n"で改行を付け加えていました。

そのC#アプリで吐いたCSVを読み込む Excel のマクロを実行すと、C# で ¥n で付けてた改行が認識されません。

おかしいと思い、出力した CSV をバイナリエディタでみると改行コードが、0x0A(LF)だけになってました。。

Excel の VBA ではファイルを OPEN した後、下記のコードで1行ずつ読んでいました。
Line Input #intFF, recode

この Line Input はどうやら改行コード LF を解釈してくれず、改行を認識するのに CR(0x0D) が必要らしいです。

なので C# 側で、改行を ¥r¥n とするか、Environment.NewLine プロパティを使う方法が取れるようです。
(Environment.NewLineだとUNIX 以外のプラットフォームでは "¥r¥n" を含む文字列で UNIX プラットフォームでは "¥n" を含む文字を表すらしい)

参考
DOBON.NET:改行、タブ文字などの定数


C#側も直すべきですが、VBA側も何とかしたいと思って調べてみました。
すると、「Line Input # ステートメント」の仕様 - 鳶秋の日記というブログを発見。
なるほど、LF で Split して配列化すればいいようです。

ファイルサイズが大きいと危険と書いてますが、今回はサイズ小さいので大丈夫でしょう。。

参考:
改行コードに注意|VBA (マクロ) 作法/コーディング規約 集約中 ~ メタボなプログラムにサヨナラしよう
[PR]
by jehoshaphat | 2010-04-26 00:37 | VBA、マクロ | Trackback | Comments(0)
(.Net)フォームを非表示で起動する

タスクトレイに常駐するアプリケーションを作ってます。指定された時間おきにWEBコンテンツの更新を確認するツールなんですが、マルチスレッド構成とし、コンテンツの更新確認は別スレッドで行っています。

で、フォームを非表示にして起動する方法ですが、とりあえずDOBON.NET:フォームを表示させずにトレイアイコンを表示するを参考にしました。

最初にとった方法はパラメータを指定せずにApplication.Runを呼び出す方法です。
こんな感じです。(C#)

/// <summary>
/// アプリケーションのメイン エントリ ポイントです。
/// </summary>
[STAThread]
static void Main()
{
//フォーム(Form1)のインスタンスを作成
Form1 f1 = new Form1();
//メッセージループを開始する
Application.Run();
}

このアプリケーションはサブスレッドからメインスレッドで生成したダイアログウィンドウを表示して、コンテンツの更新があったらそのダイアログウィンドウに表示するようにしたいんですが、上記のApplication.Runを呼び出す方法だとそれができません。

で、悩んでいたら[C#] 常駐アプリ+マルチ スレッドは要注意: いげ太のブログというのを見つけました。
なるほど、上記の方法だとコントロールがどのスレッドにもバインドされてないからダメということですかね。常駐アプリでマルチスレッドはややこしいそうです。
とりあえず、上記ブログで言及されている CreateParams をオーバーライドする方法をとってみました。(DOBON.NETの方にも出ててます。)

しかしやってみたものの、何故かフォームが表示されてしまいます。
悩んだ挙句気付いたのがデザイナの方で、WindowState プロパティを Minimized にしていました。
このプロパティを Normal にすると非表示で起動可能となりまあした。
こんな単純なことに気付くのに1時間かかってしまいましたよorz...
[PR]
by jehoshaphat | 2010-04-26 00:33 | .Net開発 | Trackback | Comments(0)
(.Net,ADSI)新規ユーザ作成直後のパスワードの変更とアカウント有効
.Net から ActiveDirectory の新規ユーザ作成すると、デフォルトでアカウントが無効となっています。
でも、userAccountControl フラグを設定して Commit すると有効になるようです。(もしくは、先にコミットしてから userAccountControl フラグ編集し、再度 Commit という手もありますが。。)
前者の場合のソースです。(C#)

//mDrctEntryは
DirectoryEntry mDrctEntry = new DirectoryEntry("LDAP://domaincntrlsvname/DC=testdomain,DC=local", "administrator", "adminpasswd");
//OUオブジェクト取得
DirectoryEntry entOu = mDrctEntry.Children.Find("OU=" + "testOU");
 
// ユーザオブジェクトを指定したOUに作成
DirectoryEntry dEntUsr = entOu.Children.Add("CN=" + "hoge tarou", "user");
// ユーザオブジェクトに各種情報追加
dEntUsr.Properties["samAccountName"].Value = "hoge001";
dEntUsr.Properties["displayName"].Value = "hoge tarou";
dEntUsr.Properties["sn"].Value = "hoge";
dEntUsr.Properties["givenName"].Value = "tarou";
dEntUsr.Properties["userPrincipalName"].Value = "hoge tarou";
 
//アカウントオプション。パスワード無期限と、通常ユーザ指定。ここで変更するとアカウント有効になる。
int val = cDONT_EXPIRE_PASSWORD | cNORMAL_ACCOUNT;
//アカウントオプションをユーザーオブジェクトに設定
dEntUsr.Properties["userAccountControl"].Value = val;
 
//新規ユーザオブジェクトをサーバに保存
dEntUsr.CommitChanges();
//保存した後にパスワードセット
dEntUsr.Invoke("SetPassword", "newPassword!1");
//dEntUsr.Invoke("ChangePassword", "oldPassword!1", "newPassword!1");//旧入れてOKなら新入れる方法
//dEntUsr.Password = user.Password; //この方法は何故かダメ。
//dEntUsr.CommitChanges();


ユーザアカウントのパスワードを変更する方法ですが、下記の2種類あるようです。

//管理者権限持ってる場合に強制的にパスワード変更する。MMCのユーザ管理でコンテキストメニューの"パスワードの設定"と同じ動作
DirectoryEntry.Invoke("SetPassword", "new password");
//ユーザが自分の権限でも行えるパスワード変更。旧パスワードを指定し新しいパスワードを入れる
DirectoryEntry.Invoke("ChangePassword", "old password" , "new password");


あと、DirectoryEntry.Password というプロパティがあって、セッターのみあったんですが、ためしにここにパスワード文字列指定してみましたが、これだとちゃんと設定できませんでした。
まぁMSDN:ユーザー パスワードの管理でもInvokeで、SetPassword,ChangePasswordを使う方法が書いているんでそっちが正道ということでしょうか。

参考:
[ADSI]ActiveDirectoryのユーザのパスワードを管理する - ComponentGeek Article
[ADSI] ユーザ状態による、SetPassword, ChangePassword, ValidateCredentials(ユーザ検証)の成否メモ - ComponentGeek Article アカウントオプションのステータスでそれぞれのパスワード変更メソッドが動作するかどうかの検証がなされています。
[PR]
by jehoshaphat | 2010-04-22 13:01 | .Net開発 | Trackback | Comments(0)
(.Net,ADSI)ActiveDirectoryでユーザアカウントの「ユーザはパスワードを変更できない」オプション設定
MSDN:UserAccountControl フラグを使用してユーザー アカウント プロパティを操作する方法にある UserAccountControl フラグで、PASSWD_CANT_CHANGE フラグ(0x0040) を使って仕様と思ったが、これは使えませんでした。
上記MSのページにも注意事項として「注 : UserAccountControl 属性を直接変更することでこのアクセス許可を割り当てることはできません。プログラムを使用してアクセス許可を設定する方法の詳細については、この資料の「プロパティ フラグの説明」を参照してください。 」とあります。
説明ページへのリンクがあるんですがは英文で読めません。。。


で、見つけたのが、[ADSI] Active Directory 環境でユーザはパスワードを変更できないオプションをコントロールする - ComponentGeek Articleです。(このサイトはActiveDirectory操作に関する情報が満載で助かってます。)

パスワードオプションの変更には、ActiveDirectoryAccessRule クラスとやらを使わないといけないようですね。

ほぼ参考先のソースと同じですが、一応のっけておきます。(C#, LINQ使ってるので.Net3.5以上となります。)

/// <summary>
/// パスワードの変更の許可・拒否を行う。
/// </summary>
/// <param name="dEntUsr">ユーザーオブジェクトのパス</param>
/// <param name="allowChangePassword">True:パスワード変更許可 False:パスワード変更拒否</param>
private void SetCannotChangePassword(string path, bool allowChangePassword)
{
//Pathからオブジェクト取得
DirectoryEntry dEntUsr = new DirectoryEntry(path, cUser, cPasswd);
//DirectoryEntry dEntUsr = (DirectoryEntry)entry.NativeObject;
 
// パスワードの変更を許可,拒否する、特別なタスクの実行を許可するための権利を表すオブジェクトのGUID
Guid changePasswordGuid = new Guid("AB721A53-1E2F-11D0-9819-00AA0040529B");
 
// Everyoneのセキュリティ識別子
SecurityIdentifier everyoneSid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
 
// NT AUTHORITY\SELFのセキュリティ識別子.
SecurityIdentifier selfSid = new SecurityIdentifier(WellKnownSidType.SelfSid, null);
 
//EveryoneのActiveDirectoryアクセス規則オブジェクト生成
ActiveDirectoryAccessRule allowEveryone = new ActiveDirectoryAccessRule(everyoneSid, ActiveDirectoryRights.ExtendedRight, AccessControlType.Allow, changePasswordGuid);
ActiveDirectoryAccessRule denyEveryone = new ActiveDirectoryAccessRule(everyoneSid, ActiveDirectoryRights.ExtendedRight, AccessControlType.Deny, changePasswordGuid);
//NT AUTHORITY\SELFのActiveDirectoryアクセス規則オブジェクト生成
ActiveDirectoryAccessRule allowSelf = new ActiveDirectoryAccessRule(selfSid, ActiveDirectoryRights.ExtendedRight, AccessControlType.Allow, changePasswordGuid);
ActiveDirectoryAccessRule denySelf = new ActiveDirectoryAccessRule(selfSid,ActiveDirectoryRights.ExtendedRight, AccessControlType.Deny, changePasswordGuid);
 
//ユーザアカウントオブジェクトのセキュリティ制御オブジェクト生成
ActiveDirectorySecurity ads = dEntUsr.ObjectSecurity;
//ユーザアカウントオブジェクトのアクセス規則のコレクションを取得
AuthorizationRuleCollection accessRules = ads.GetAccessRules(true, false, typeof(NTAccount));
//LINQでパスワード許可拒否権利のある AB721A53-1E2F-11D0-9819-00AA0040529B に一致するルールを取得
var query = from rule in accessRules.OfType<ActiveDirectoryAccessRule>()
where rule.ObjectType == changePasswordGuid
select rule;
 
foreach (ActiveDirectoryAccessRule rule in query)
{
bool modified = false;
if (rule.IdentityReference.Value == @"NT AUTHORITY\SELF")
{
if (allowChangePassword && rule.AccessControlType == AccessControlType.Deny)
{
//パスワード変更許可にする
ads.ModifyAccessRule(AccessControlModification.Reset, allowSelf, out modified);
}
else if (!allowChangePassword && rule.AccessControlType == AccessControlType.Allow)
{
//パスワード変更拒否にする
ads.ModifyAccessRule(AccessControlModification.Reset, denySelf, out modified);
}
}
else if (rule.IdentityReference.Value == @"Everyone")
{
if (allowChangePassword && rule.AccessControlType == AccessControlType.Deny)
{
//パスワード変更許可にする
ads.ModifyAccessRule(AccessControlModification.Reset, allowEveryone, out modified);
}
else if (!allowChangePassword && rule.AccessControlType == AccessControlType.Allow)
{
//パスワード変更拒否にする
ads.ModifyAccessRule(AccessControlModification.Reset, denyEveryone, out modified);
}
 
}
}
//設定を保存
dEntUsr.CommitChanges();
 
}


パスはDirectoryEntryで取得したUserオブジェクトの distinguishedName(識別名 DN,OU,DC,JP)が使えるようです。

また、上記ソースで出てくる GUID:"AB721A53-1E2F-11D0-9819-00AA0040529B" というのは@IT:セキュリティ設定を記述するSDDL文字列とは?によると、User-Change-Password という名前で、パスワードの変更権利を意味するらしいですね。
[PR]
by jehoshaphat | 2010-04-22 13:00 | .Net開発 | Trackback | Comments(0)
Windows日本語版で中国語(簡体字)ソフトが動かない

中国から出稼ぎに来てる方が日本で買ったPCで、中国語のストリーミングソフト(PPS)が動かないのでみてほしいという依頼がありました。

確認しに行ったところ、OSはWindows7日本語版で、ストリーミングソフトを起動するとメニュー等が文字化けしており、再生もできませんでした。

おそらく文字化けということで、ロケールが正しくなっていないかと思い、コントロールパネルの地域と言語から確認してみます。

ユーザロケール(地域と言語の形式タブ)は中国語(簡体字)になってましたが、システムロケール(地域と言語の管理タブ→Unicode対応でないプログラムの言語)が日本語のままでした。
ここを中国語に変更すると正しく動作しました。(システムロケールの変更は再起動が必要です。)

普段ロケールなんて触る機会ないので四苦八苦でしたねw
[PR]
by jehoshaphat | 2010-04-22 12:59 | 豆知識 | Trackback | Comments(0)
(.Net)DataGridViewへのカスタムクラスのバインド
(.Net)DataGridViewでバインドしたデータソースを変更してもグリッドには反映されない?でカスタムクラスのバインドについて書きました。

あの方法でバインドした DataGridView を下記のようにプログラム側から1行削除しようとしました。

public partial class Form1 : Form
{
//カスタムクラスのList。バインド元になるデータ。
List<user> mlst = new List<user>();

private void button1_Click(object sender, EventArgs e)
{
mlst.Add(new user("hogehoge1", "1"));
mlst.Add(new user("hogehoge2", "2"));
mlst.Add(new user("hogehoge3", "3"));
}
 
private void Form1_Load(object sender, EventArgs e)
{
//バインド。
//dataGridView1.DataSource = mlst;
}

private void button2_Click(object sender, EventArgs e)
{
dataGridView1.Rows.RemoveAt(0);
}
}
 
//カスタムクラス。
public class user
{
public string name { get; set; }
public string id { get; set; }
public user(string sname, string sid)
{
name = sname;
id = sid;
}
}

Button1を押下しレコード追加後、Button2を押下してレコードを削除します。

その時、RemoveAt で下記の例外が発生しました。

System.InvalidOperationException はハンドルされませんでした。
Message="変更通知をサポートし、削除を許可する IBindingList に DataGridView がデータバインドされていない限り、プログラムで行を削除することはできません。"
Source="System.Windows.Forms"
StackTrace:
場所 System.Windows.Forms.DataGridViewRowCollection.RemoveAt(Int32 index)
場所 DataGridViewBindObjectTest.Form1.button2_Click(Object sender, EventArgs e) 場所 D:\mydoc\DataGridViewBindObjectTest\DataGridViewBindObjectTest\DataGridViewBindObjectTest\Form1.cs:行 65
...



で、調査してみました。
するとデータバインディングのおべんきょ。その5。で List をバインド対象として使わないほうがいいと書いてあります。理由として、List クラスが IBindingList インターフェイスを実装してないからみたいですね。
もうちょっと詳しい話が、MSDN:カスタム データ バインド (第 2 部)に下記のように書いてます。
BindingList<T> は IBindingList インターフェイス (System.ComponentModel 名前空間) のジェネリック実装です。また、リスト データ ソースで編集機能をサポートするために、データ バインドインフラストラクチャで最低限必要になるインターフェイスです。IList はバインド可能なリスト データソースの実装に必要な最低限のインターフェイスですが、このインターフェイスでは編集機能は提供されません。これは、ListBox などの編集できないコントロールにバインドされる場合は問題ありません。ただし、DataGridView など、編集機能を完全にサポートしているコントロールにバインドされる場合は、並べ替え、検索、インデックス作成、および変更通知などの機能のサポート以外に、リスト データ ソース内での編集機能が必須になります。IBindingList は IList から派生しているので、このような機能をすべてサポートするように拡張されます。

つまり、IListを継承した List<T>クラスは編集機能を有さない最低限の機能しかないためダメだったんですね。

かといって、今更BindingListに直すのも面倒なんでいい方法が無いかいろいろ試してみました。
見つけたのが、MSDN:方法 : オブジェクトを Windows フォーム DataGridView コントロールにバインドするです。
BindingSource を使ってますね。
DataGridView → BindingSource → コレクション という感じに使うようです。

ただ、MSDNのチュートリアルでは BindingSource の Add メソッドでカスタムクラス単体を追加しています。
本来はこの方法か、iBindingList を実装したクラスでバインドするのが筋なんでしょうが、面倒なので、下記のようにしました。

public partial class Form1 : Form
{
List<user> mlst = new List<user>();
 
private void button1_Click(object sender, EventArgs e)
{
mlst.Add(new user("hogehoge1", "1"));
mlst.Add(new user("hogehoge2", "2"));
mlst.Add(new user("hogehoge3", "3"));
bindingSource1.DataSource = null;
bindingSource1.DataSource = mlst;
}
 
private void Form1_Load(object sender, EventArgs e)
{
DataColumn clm1 = new DataColumn("name");
mTbl.Columns.Add(clm1);
DataColumn clm2 = new DataColumn("id");
mTbl.Columns.Add(clm2);
 
dataGridView1.DataSource = bindingSource1;
}
 
private void button2_Click(object sender, EventArgs e)
{
dataGridView1.Rows.RemoveAt(0);
}
}
 
public class user
{
public string name { get; set; }
public string id { get; set; }
 
public user(string sname, string sid)
{
name = sname;
id = sid;
}
}

BindingSource の DetaSource に List を指定しています。
とりあえずレコードの削除やDataGridView上の変更も List オブジェクトに反映されてるようです。

あんまきれいなやり方では無いですが、とりあえずこれでやってみることとしました。


今まで DataGridView にバインドしてたのは DataTable で楽してたんですが、他のコレクションをバインドするのは結構機能面の制約があって面倒ですね。。。
[PR]
by jehoshaphat | 2010-04-18 07:35 | .Net開発 | Trackback | Comments(0)