「ほっ」と。キャンペーン
カテゴリ:.Net開発( 216 )
(.Net)改行無しの全銀フォーマットを可読化する

全銀ファイルフォーマットを扱うことになったんですが、銀行側からくる口座振替のファイルをみて驚きました。
各レコードを区別する改行が無いのです。
全銀フォーマットが固定長なのは知ってましたが、改行が無いのは見づらいです。

ということで改行無の全銀フォーマットを見やすくするアプリを作りました。

取り合えず下記の要件を実装します。
・120桁おきに改行コードを入れてテキストに出力
・口座振替のフォーマットに従って各フィールドをカンマ区切りにしCSV出力
テキストボックス txtTargetFile.Text に読み込むファイルパスが格納されており、ラジオボタン rdoToCSV がチェック有りなら上記要件の2つ目CSV出力とします。

ソースはこんな感じです。ざっと作ったのできれいじゃないところも有りますが。(C#)

private void btnOK_Click(object sender, EventArgs e){
//1行の長さ
int cLineLength = 120;
//ファイル読み込み
string strFile = File.ReadAllText(txtTargetFile.Text, Encoding.GetEncoding("Shift_JIS"));
bool bHeadItemOut = false;
bool bDataItemOut = false;
bool bTrailerItemOut = false;
int idx = 0;
//変換後の結果格納
StringBuilder strOutput = new StringBuilder();
while (idx < strFile.Length){
//120文字読み込み
string strLine = strFile.Substring(idx, cLineLength);
//CSVにしたいなら
if (rdoToCSV.Checked){
int lIdx = 0;
int[] fWidth = new int[] { };
int iDataType = 0;
if (int.TryParse(strLine.Substring(0, 1), out iDataType)){
switch (iDataType){
case 1:
//ヘッダレコード
if (!bHeadItemOut){
strOutput.AppendLine("データ区分,種別コード,コード区分,依頼人コード,依頼人名,引落日,取引金融機関番号,取引金融機関名,取引支店番号,取引支店名,預金種目(委託者),口座番号(委託者),ダミー");
bHeadItemOut = true;
}
fWidth = new int[] { 1, 2, 1, 10, 40, 4, 4, 15, 3, 15, 1, 7, 17 };
break;
case 2:
//データレコード
if (!bDataItemOut){
strOutput.AppendLine("データ区分,引落金融機関番号,引落金融機関名,引落支店番号,引落支店名,ダミー,預金種目,口座番号,預金者名,振替金額,新規コード,顧客番号,振替結果コード,ダミー");
bDataItemOut = true;
}
fWidth = new int[] { 1, 4, 15, 3, 15, 4, 1, 7, 30, 10, 1, 20, 1, 8 };
break;
case 8:
//トレーラレコード
if (!bTrailerItemOut){
strOutput.AppendLine("データ区分,合計件数,合計金額,振替済件数,振替済金額,振替不能件数,振替不能金額,ダミー,預金者名,振替金額,新規コード,顧客番号,振替結果コード,ダミー");
bDataItemOut = true;
}
fWidth = new int[] { 1, 6, 12, 6, 12, 6, 12, 65 };
break;
case 9:
//エンドレコード
fWidth = new int[] { 1, 119 };
break;
default:
break;
}
foreach (int width in fWidth){
strLine = strLine.Insert(lIdx + width, ",");
lIdx = ++lIdx + width;
}
}else{
idx += cLineLength;
continue;
}
}
strOutput.AppendLine(strLine);
idx += cLineLength;
}
//保存
SaveFileDialog dlg = new SaveFileDialog();
dlg.InitialDirectory = Path.GetDirectoryName(txtTargetFile.Text);
dlg.FileName = Path.GetFileNameWithoutExtension(txtTargetFile.Text) + "_可読化";
dlg.Filter = "textファイル(*.txt)|*.txt|csvファイル(*.csv)|*.csv|すべてのファイル(*.*)|*.*";
if (rdoToCSV.Checked)
dlg.FilterIndex = 2;
if (dlg.ShowDialog() == DialogResult.OK){
File.WriteAllText(dlg.FileName, strOutput.ToString(), Encoding.GetEncoding("Shift_JIS"));
}
}

[PR]
by jehoshaphat | 2011-08-25 12:11 | .Net開発 | Trackback | Comments(2)
(.Net)SaveFileDialogで保存したファイルに拡張子がつかない
ファイルの保存ダイアログを SaveFileDialog で実装しようとしてました。

SaveFileDialog の AddExtension プロパティを true にすると現在のフィルタで選んでいる拡張子が自動的に付きます。
(デフォルトでAddExtensionはtrueです)

しかし、なぜか拡張子が付きません。。。

原因は、ファイル保存ダイアログを表示する前に SaveFileDialog.FileName でファイル名を指定してたんですが、それに "." があったためでした。

下記のような感じだったわけです。(C#)


SaveFileDialog dlg = new SaveFileDialog();
dlg.FileName = "2011.3.1";
dlg.Filter = "textファイル(*.txt)|*.txt|csvファイル(*.csv)|*.csv|すべてのファイル(*.*)|*.*";
dlg.AddExtension = true;
//ダイアログを表示する
if (dlg.ShowDialog() == DialogResult.OK)
{
MessageBox.Show(dlg.FileName);
}

どうやら FileName に "."(ドット/ピリオド) にあると拡張子が既にあると判断されて、つけてくれないようですね。
厄介な仕様です。
[PR]
by jehoshaphat | 2011-08-18 12:22 | .Net開発 | Trackback | Comments(0)
(.Net)オブジェクトのメンバ情報とメンバの値を動的に取得する
オブジェクト指向だと値を格納する目的のクラスってよく使います。(例えば、社員クラスとか...)

で、それらのデータ格納用クラスのメンバ変数を一気に出力するのを汎用的にするというのが今回の目的です。

例えば下記のようなクラスがあるとします。

public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime Birthday { get; set; }
public List FamilyNames { get; set; }
}

public class Car
{
public string CarName { get; set; }
public string Engine { get; set; }
public int DryWeight { get; set; }
public int HorsePower { get; set; }
}


汎用的(つまりどのクラスでも使えるよう動的)にするには下記のようにします。(汎用的でない例も入れてます)(C#)
private void button1_Click(object sender, EventArgs e)
{
//テストクラスのインスタンス生成(.Net 3.0以降で使えるクラス初期化の方法)
Person prs = new Person()
{
FirstName = "礼司",
LastName = "安部",
Birthday = new DateTime(1971, 10, 10),
FamilyNames = new List<string> { "安部優", "安部永太","ちび安部" }
};
Car car = new Car() {
CarName = "GT-R",
Engine = "VR38DETT",
DryWeight = 1740,
HorsePower = 480
};
 
Console.WriteLine("--------------汎用的でない出力の仕方--------------");
Console.WriteLine("FirstName 値 : " + prs.FirstName);
Console.WriteLine("LastName 値 : " + prs.LastName);
Console.WriteLine("Birthday 値 : " + prs.Birthday.ToString());
Console.Write("FamilyNames 値 : ");
foreach (string item in prs.FamilyNames)
{
Console.Write(item+",");
}
Console.WriteLine("\n\n--------------汎用的な出力の仕方--------------");
//prsオブジェクトのメンバと値を出力。typeof(型)を忘れずに。
OutputProperty(prs,typeof(Person));
//carオブジェクトのメンバと値を出力
OutputProperty(car, typeof(Car));
 
//prsオブジェクトのListの場合は下記のようにすればよろし
List<Person> lstp = new List<Person>();
lstp.Add(prs);
lstp.Add(new Person() { FirstName = "aaa", LastName = "bbb", Birthday = new DateTime(1971, 10, 10) });
lstp.Add(new Person() { FirstName = "ccc", LastName = "sss", Birthday = new DateTime(1971, 10, 10) });
Console.WriteLine("\n\n--------------汎用的な出力の仕方(List版)--------------");
//出力。ListをArrayListにすることが肝かな。
OutputPropertyArray(new ArrayList(lstp), typeof(Person));
}
 
/// <summary>
/// オブジェクトのメンバを取得しプロパティなら表示する。
/// </summary>
/// <param name="targetobj">クラスのインスタンス</param>
/// <param name="t">クラスのTypeオブジェクト</param>
private void OutputProperty(object targetobj, Type t)
{
//using System.Reflection;が必要
//メンバを取得する
MemberInfo[] members = t.GetMembers(
BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.DeclaredOnly);
 
foreach (MemberInfo m in members)
{
//メンバの型と、名前を表示する
//メンバの型がプロパティなら
if (m.MemberType.ToString().Equals("Property"))
{
Console.WriteLine("メンバ型:{0} - メンバ名:{1}", m.MemberType, m.Name);
PropertyInfo pr = t.GetProperty(m.Name);
object resobj = pr.GetValue(targetobj, null);
//List<string>ならカンマ区切りで表示
if (resobj != null && typeof(List<string>).IsAssignableFrom(resobj.GetType()))
{
List<string> lstRes = (List<string>)resobj;
//LINQを使用してList<string>をカンマ区切りで結合する。(ループで回してもいいんだけどこっちの方がシンプルなんで...)
Console.WriteLine(" 値 : " + string.Join(", ", lstRes.ToArray()) );
}
else if (resobj != null)
{
Console.WriteLine(" 値 : " + resobj.ToString());
}
}
}
}
 
/// <summary>
/// Listコレクション
/// </summary>
/// <param name="targetobj">クラスのインスタンスのコレクション</param>
/// <param name="t">クラスのTypeオブジェクト</param>
private void OutputPropertyArray(ArrayList targetobj, Type t)
{
foreach (var item in targetobj)
{
OutputProperty(item, t);
}
}



オブジェクトのメンバの型情報を取得するには、クラスの Type 情報を取得し、GetMembers メソッドで取得できます。
この際、どこまでを含めるかのフラグの指定は、MSDN:BindingFlags 列挙体 (System.Reflection)を使います。


メンバの値の取得は、プロパティの場合 Type.GetProperty("名前") で PropertyInfo オブジェクトを取得し、GetValue メソッドで可能です。
メソッドの場合は MethodInfo 、フィールドの場合は FieldInfo クラスを使えるようですね。


取得したプロパティの値が List かどうかは、(.Net)objectがListかまたは配列かどうか判定する で詳しく書いています。

上記コードを実行した結果は下記のようになります。

--------------汎用的でない出力の仕方--------------
FirstName 値 : 礼司
LastName 値 : 安部
Birthday 値 : 1971/10/10 0:00:00
FamilyNames 値 : 安部優,安部永太,ちび安部,

--------------汎用的な出力の仕方--------------
メンバ型:Property - メンバ名:FirstName
値 : 礼司
メンバ型:Property - メンバ名:LastName
値 : 安部
メンバ型:Property - メンバ名:Birthday
値 : 1971/10/10 0:00:00
メンバ型:Property - メンバ名:FamilyNames
値 : 安部優, 安部永太, ちび安部
メンバ型:Property - メンバ名:CarName
値 : GT-R
メンバ型:Property - メンバ名:Engine
値 : VR38DETT
メンバ型:Property - メンバ名:DryWeight
値 : 1740
メンバ型:Property - メンバ名:HorsePower
値 : 480


--------------汎用的な出力の仕方(List版)--------------
メンバ型:Property - メンバ名:FirstName
値 : 礼司
メンバ型:Property - メンバ名:LastName
値 : 安部
メンバ型:Property - メンバ名:Birthday
値 : 1971/10/10 0:00:00
メンバ型:Property - メンバ名:FamilyNames
値 : 安部優, 安部永太, ちび安部
メンバ型:Property - メンバ名:FirstName
値 : aaa
メンバ型:Property - メンバ名:LastName
値 : bbb
メンバ型:Property - メンバ名:Birthday
値 : 1971/10/10 0:00:00
メンバ型:Property - メンバ名:FamilyNames
メンバ型:Property - メンバ名:FirstName
値 : ccc
メンバ型:Property - メンバ名:LastName
値 : sss
メンバ型:Property - メンバ名:Birthday
値 : 1971/10/10 0:00:00
メンバ型:Property - メンバ名:FamilyNames



プロジェクト内で扱うクラスの数が多くて、クラス内メンバの値が一気にほしい時などはこのようにリフレクションを使ってやると便利ですね。

参考:
dobon.net:型(クラス、構造体など)のすべてのメンバを取得する オブジェクトからメンバの型情報を取得する方法について解説されています。
dobon.net:型のメンバを動的に呼び出す オブジェクトのメンバの値を取得する方法です。今回はページ中ほどの「MethodInfo、ConstructorInfo、PropertyInfo、FieldInfoクラスを使用した方法」を使っています。
[PR]
by jehoshaphat | 2011-08-12 12:28 | .Net開発 | Trackback | Comments(0)
(.Net)List<string>で一気に文字列の連結をしたい
Listで、それぞれの要素の文字列を結合して一つの文字列にしたいという場合の方法です。
ループで回して結合してもいいんですが、それだとコードが長くなるので、LINQString.Join()を使ってみました。

下記のような感じです。(C#)


List<string> str = new List<string>{ "あいうえお", "かきくけこ", "さしすせそ"};
string joinedString = string.Join(", ", str.ToArray());



LINQ使うと簡単ですね。

追記:
誤ってLINQを使っていると書いてますが、全然使ってませんねw。
String.Join メソッドは.NetFramework1.1からある老舗メソッドです。
[PR]
by jehoshaphat | 2011-08-12 12:23 | .Net開発 | Trackback | Comments(0)
(.Net)objectがList<T>かまたは配列かどうか判定する
配列変数やジェネリックコレクション(List)をobjectにしてしまった時に、Listか配列か判定する方法です。



配列かどうかを判断にするにはオブジェクトの GetType().IsArray で可能なようです。
型指定なしのジェネリックコレクション(List<>)かどうかを判定するには、typeof(List<>).IsAssignableFrom(オブジェクト.GetType().GetGenericTypeDefinition()) とするといいようです。(ただし、配列の変数にこれをすると例外が発生するので、配列じゃないことを確認した後に動かす必要があります)
型指定ありのジェネリックコレクション(List)かどうかを判定するには、typeof(List).IsAssignableFrom(オブジェクト.GetType()) とするといいようです。

下記にサンプルコードを載せてみました。(C#)

//List<T>コレクション
object lstStr = new List<string>() {"aaa","bbb","ccc","ddd" };
object lstInt = new List<int>() { 1, 2, 3 };
//配列
object aryStr = new string[] { "aaa", "bbb", "ccc", "ddd" };
object aryInt = new int[] { 1, 2, 3 };
 
//List<> かどうかチェック
if (typeof(List<>).IsAssignableFrom(lstStr.GetType().GetGenericTypeDefinition()))
Console.WriteLine(" lstStrはList<>である");
else
Console.WriteLine(" lstStrはList<>でない");
 
 
//List<T>かどうかチェック
if (typeof(List<string>).IsAssignableFrom(lstStr.GetType()))
Console.WriteLine(" lstStrはList<string>である");
else
Console.WriteLine( " lstStrはList<string>でない");
 
if (typeof(List<int>).IsAssignableFrom(lstInt.GetType()))
Console.WriteLine( " lstIntはList<int>である");
else
Console.WriteLine( " lstIntはList<int>でない");
 
if (typeof(List<string>).IsAssignableFrom(aryStr.GetType()))
Console.WriteLine( " aryStrはList<string>である");
else
Console.WriteLine( " aryStrはList<string>でない");
 
if (typeof(List<int>).IsAssignableFrom(aryInt.GetType()))
Console.WriteLine( " aryIntはList<int>である");
else
Console.WriteLine( " aryIntはList<int>でない");
 
 
 
 
//配列かどうかチェック Type.IsArrayプロパティで判定
if (lstStr.GetType().IsArray)
Console.WriteLine( " lstStrは配列である");
else
Console.WriteLine( " lstStrは配列でない");
 
if (lstInt.GetType().IsArray)
Console.WriteLine(" lstIntは配列である");
else
Console.WriteLine( " lstIntは配列でない");
 
 
if (aryStr.GetType().IsArray)
Console.WriteLine( " aryStrは配列である");
else
Console.WriteLine( " aryStrは配列でない");
 
if (aryInt.GetType().IsArray)
Console.WriteLine( " aryIntは配列である");
else
Console.WriteLine( " aryIntは配列でない");


結果はこうなります。

lstStrはList<>である
lstStrはListである
lstIntはListである
aryStrはListでない
aryIntはListでない
lstStrは配列でない
lstIntは配列でない
aryStrは配列である
aryIntは配列である



Type.IsAssignableFrom というメソッドで型が同じかどうか判断できるようです。(厳密には同じではなくても継承可能な階層内、実装されたインターフェイス等でもtrueになるようです。)


あんまり object になんでもかんでも代入することは無いのですが、今回は動的にオブジェクトのメンバを取得し、その中にコレクション型のメンバがいたのでこのような判定が必要になりました。

参考:
@IT 会議室:objectが配列型かどうか判定する方法について 大変役に立つスレでした。ほぼコピペさせてもらってます。
[PR]
by jehoshaphat | 2011-08-12 12:20 | .Net開発 | Trackback | Comments(0)
(.Net)メッセージボックスを最前面に表示したい
現在 BackgroundWorker コンポーネントを使ったマルチスレッドアプリケーションを使ってます。
メインスレッドはフォームのUIを表示し、別スレッドで動くBackgroundWorker.DoWorkイベントハンドラで時間のかかる処理をしています。

この別スレッド側でメッセージボックスを表示したときに、メインスレッドのフォームUIより手前に表示したい かつ 最前面に表示というのが今回の要件です。
(ちなみに、フォームの TopMost プロパティが true でも最前面に表示になるので、メッセージボックスをお手製で作って TopMost プロパティを使うという方法もとれるかもしれません。)

MessageBox クラスには IWin32Window を指定することで、指定したオブジェクより前面に表示する Show メソッドがありますが、今回は最前面に表示したいので、その方法は使いませんでした。

で、調べると、MessageBoxOptions 列挙体MessageBox.Show メソッドに指定することによって最前面に表示ができるようです。

MessageBoxOptions 列挙体で、DefaultDesktopOnly か ServiceNotification を指定したら最前面に表示されました。他のウィンドウに隠れることはありません。
ただし、visual スタイルを使用できないで、古臭いUIにはなります。

DefaultDesktopOnly と ServiceNotification の違いですが、DefaultDesktopOnlyでは既定のデスクトップ(ユーザーがログオンした後でアプリケーションが動作するデスクトップ)でのみ表示され、ServiceNotificationはログインしているユーザーがいない場合でもメッセージボックスが表示されます。
ServiceNotificationは主に、サービスからのメッセージボックス表示に使われるようです。

ユーザログインしてから使うアプリケーションから表示するものだったので、DefaultDesktopOnly を指定しました。

テストソースは下記のような感じです。(C#)

MessageBox.Show("test",
"caption",
MessageBoxButtons.OKCancel,
MessageBoxIcon.Information,
MessageBoxDefaultButton.Button1,
MessageBoxOptions.DefaultDesktopOnly);



参考:
メッセージボックスを表示する: .NET Tips: C#, VB.NET, Visual Studio
Program.csでメッセージボックスを最前面に出す方法
[C#]同じこと考える人いるんですね - Program.csでメッセージボックスを最前面に出す方法
[PR]
by jehoshaphat | 2011-06-30 22:29 | .Net開発 | Trackback | Comments(0)
(.Net)File.Create(ファイル名)でファイルを作成したら必ずCloseしよう

.Net で File.Create() メソッドを使うと簡単にファイルの作成ができます。
しかし、上記MSDNにあるように、元のファイルのハンドルが閉じられるまで、作成されたファイルに他のプロセスやコードからアクセスすることはできないので、ハンドルを閉じてやる必要があります。

こんな感じです。

FileStream fs = File.Create(Path.Combine(@"c:\test","test.txt"));
fs.Close();

ただ単に空ファイルを作るだけなら下記のようにもできます。
File.Create(Path.Combine(@"c:\test","test.txt")).Close();

余談ですが、Path.Combine ってパスを作成するときに便利ですね。

参考:
C#でFile.Create(filename)したままだと、ファイルが開いたままになる
[PR]
by jehoshaphat | 2011-06-30 00:40 | .Net開発 | Trackback | Comments(0)
(.Net)Windows共有フォルダのセッションの切断する
.Net アプリケーションからWindows共有フォルダのセッションを切断する方法です。
GUIだと、コンピュータの管理 から 共有フォルダ→セッション→セッション右クリック→セッションを閉じる ということを .Net アプリケーションからしたいわけです。

コマンドプロンプトから、net session \\コンピュータ名 /delete でセッションを切断することができるので、当初 .Net アプリからこのコマンドをキックしようと思ったのですが、これが自身のPCにしかできません。また、コンピュータ名での指定しかできません。
今回の要件は .Net 側からリモートのファイルサーバの共有フォルダセッションを解除したいということです。(つまり、.NETアプリが動くPCとファイルサーバが別コンピュータ)。そして、ユーザのほとんどはターミナルサーバからログインしており、特定のユーザだけ削除したいのです。

そこで、結局 Windows API の NetSessionDel 関数を使うことにしました。このAPIだとリモートのコンピュータに対して、ユーザのPC名、ユーザ名を指定してセッション切断ができます。

下記のような感じでセッション切断できます。(C#)

/// <summary>
/// セッションを閉じる
/// </summary>
/// <param name="strServerName">サーバ名(\\servernameの形式) null なら自身を表す。</param>
/// <param name="strClientHostName">クライアントホスト名(\\servernameの形式)NULL を指定すると、username パラメータで指定されたユーザーのすべてのセッションは、servername パラメータで指定されたサーバーから削除</param>
/// <param name="strUserName">ユーザ名。NULL を指定すると、UncClientName パラメータで指定されたクライアント名に関係する、すべてのユーザーのセッションが終了</param>
/// <returns>Win32エラーコード</returns>
public static NET_API_STATUS CloseSession(string strServerName, string strClientHostName, string strUserName) {
StringBuilder strBulSvname = null ;
if (! String.IsNullOrEmpty(strServerName) )
strBulSvname = new StringBuilder(strServerName);
 
StringBuilder strBulClientHostName = null;
if ( ! String.IsNullOrEmpty(strClientHostName))
strBulClientHostName= new StringBuilder(strClientHostName);
 
StringBuilder strBulUserName = null;
if ( ! String.IsNullOrEmpty(strUserName))
strBulUserName = new StringBuilder(strUserName);
 
NET_API_STATUS res = NetSessionDel(strBulSvname, strBulClientHostName, strBulUserName);
 
Console.WriteLine(res.ToString());
return res;
 
}
 
//WindowsAPI使用定義
[DllImport("netapi32.dll" ,CharSet= CharSet.Unicode)]
private static extern NET_API_STATUS NetSessionDel(
StringBuilder servername,
StringBuilder UncClientName,
StringBuilder username);
/// <summary>
/// Lmcons.h
/// #define NET_API_STATUS DWORD
/// </summary>
public enum NET_API_STATUS : uint {
NERR_Success = 0,
/// <summary> This computer name is invalid.</summary>
NERR_InvalidComputer = 2351,
/// <summary>This operation is only allowed on the primary domain controller of the domain.</summary>
NERR_NotPrimary = 2226,
/// <summary>This operation is not allowed on this special group.</summary>
NERR_SpeGroupOp = 2234,
/// <summary>This operation is not allowed on the last administrative account.</summary>
NERR_LastAdmin = 2452,
/// <summary>The password parameter is invalid.</summary>
NERR_BadPassword = 2203,
/// <summary>The password does not meet the password policy requirements. Check the minimum password length, password complexity and password history requirements.</summary>
NERR_PasswordTooShort = 2245,
/// <summary> The user name could not be found.</summary>
NERR_UserNotFound = 2221,
 
ERROR_ACCESS_DENIED = 5,
ERROR_NOT_ENOUGH_MEMORY = 8,
ERROR_INVALID_PARAMETER = 87,
ERROR_INVALID_NAME = 123,
ERROR_INVALID_LEVEL = 124,
ERROR_MORE_DATA = 234,
ERROR_SESSION_CREDENTIAL_CONFLICT = 1219
}



NET_API_STATUS res にはNetSessionDelの戻りのWindowsエラーコードが返ります。
エラーコードのメッセージを取得するには、(.Net)WindowsAPIのエラーコードからメッセージを取得するを参照してください。

注意点として、切断対象とするクライアントホスト名ですが、コンピュータの管理のセッションで出てくるコンピュータ名じゃないとダメなようです。(ここがコンピュータ名とIPアドレスとの時があるので。。。)
ですので、(.Net)共有フォルダにアクセスしているセッションを取得するでセッション情報を取ってきて、それのComputerNameの値を使うのがいいかもしれません。
また、NetSessionDelの引数を全てNULLにするとエラーになりました。クライアントコンピュータ名かユーザ名かはどちらかは指定しないといけないのかもしれません。

MSDNによると「Administrators または Account Operators ローカルグループのメンバだけがこの関数を実行できる」とのことです。
一般ユーザで使う場合は(.Net)別ユーザでプロセスを起動するで書いたようにAdministrator権限でmsgをキックするようにするといいと思います。



参考:
net sessionコマンドでパソコンに接続しているセッションを切断する :ITpro net sessionコマンドで切断する場合です。
[PR]
by jehoshaphat | 2011-06-30 00:34 | .Net開発 | Trackback | Comments(0)
(.Net)共有フォルダにアクセスしているセッションを取得する
Windows共有フォルダにアクセスしているセッション情報を .NET アプリケーションから取得する方法です。
クライアントPCから実行し、共有フォルダを提供しているサーバPCのセッションを取得します。

コマンドでするなら、net session で取得することができますが、共有フォルダを提供しているコンピュータでしか実行できません。また、整形して出力するので長いユーザ名だと切れてしまいそうです。(クライアントは文字列途中で切れてます)

ということで WMI で取得することにしました。

WMI Code Creator を使うと簡単にセッション取得のコードが生成できます。

C#だと下記のような感じです。

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Management;
 
namespace WMISample{
public class MyQuerySample : System.Windows.Forms.Form{
private System.Windows.Forms.Label userNameLabel;
private System.Windows.Forms.TextBox userNameBox;
private System.Windows.Forms.TextBox passwordBox;
private System.Windows.Forms.Label passwordLabel;
private System.Windows.Forms.Button OKButton;
private System.Windows.Forms.Button cancelButton;

private System.ComponentModel.Container components = null;
 
public MyQuerySample(){
InitializeComponent(); }
 
protected override void Dispose( bool disposing ) {
if( disposing ) {
if (components != null) {
components.Dispose(); }
}
base.Dispose( disposing );
}
 
private void InitializeComponent() {
//省略
}
 
[STAThread]
static void Main() {
Application.Run(new MyQuerySample());
}
 
private void OKButton_Click(object sender, System.EventArgs e) {
try {
ConnectionOptions connection = new ConnectionOptions();
connection.Username = userNameBox.Text;
connection.Password = passwordBox.Text;
connection.Authority = "ntlmdomain:hogedomain";
 
ManagementScope scope = new ManagementScope(
"\\\\fileserver\\root\\CIMV2", connection);
scope.Connect();
 
ObjectQuery query= new ObjectQuery(
"SELECT * FROM Win32_ServerConnection");
 
ManagementObjectSearcher searcher =
new ManagementObjectSearcher(scope, query);
 
foreach (ManagementObject queryObj in searcher.Get()){
Console.WriteLine("-----------------------------------");
Console.WriteLine("Win32_ServerConnection instance");
Console.WriteLine("-----------------------------------");
Console.WriteLine("ActiveTime: {0}", queryObj["ActiveTime"]);
Console.WriteLine("Caption: {0}", queryObj["Caption"]);
Console.WriteLine("ComputerName: {0}", queryObj["ComputerName"]);
Console.WriteLine("ConnectionID: {0}", queryObj["ConnectionID"]);
Console.WriteLine("Description: {0}", queryObj["Description"]);
Console.WriteLine("InstallDate: {0}", queryObj["InstallDate"]);
Console.WriteLine("Name: {0}", queryObj["Name"]);
Console.WriteLine("NumberOfFiles: {0}", queryObj["NumberOfFiles"]);
Console.WriteLine("NumberOfUsers: {0}", queryObj["NumberOfUsers"]);
Console.WriteLine("ShareName: {0}", queryObj["ShareName"]);
Console.WriteLine("Status: {0}", queryObj["Status"]);
Console.WriteLine("UserName: {0}", queryObj["UserName"]);
}
Close();
}catch(ManagementException err) {
MessageBox.Show("An error occurred while querying for WMI data: " + err.Message);
}catch(System.UnauthorizedAccessException unauthorizedErr) {
MessageBox.Show("Connection error (user name or password might be incorrect): " + unauthorizedErr.Message);
}
}
 
private void cancelButton_Click(object sender, System.EventArgs e) {
Close();
}
}
}


VBScriptだと下記のようになります。(文字数制限の関係でハイライトOFFです。)

strComputer = "fileserver"
strDomain = "hogedomain"
Wscript.StdOut.Write "Please enter your user name:"
strUser = Wscript.StdIn.ReadLine
Set objPassword = CreateObject("ScriptPW.Password")
Wscript.StdOut.Write "Please enter your password:"
strPassword = objPassword.GetPassword()
Wscript.Echo

Set objSWbemLocator = CreateObject("WbemScripting.SWbemLocator")
Set objWMIService = objSWbemLocator.ConnectServer(strComputer, _
"root\CIMV2", _
strUser, _
strPassword, _
"MS_409", _
"ntlmdomain:" + strDomain)
Set colItems = objWMIService.ExecQuery( _
"SELECT * FROM Win32_ServerConnection",,48)
For Each objItem in colItems
Wscript.Echo "-----------------------------------"
Wscript.Echo "Win32_ServerConnection instance"
Wscript.Echo "-----------------------------------"
Wscript.Echo "ActiveTime: " & objItem.ActiveTime
Wscript.Echo "Caption: " & objItem.Caption
Wscript.Echo "ComputerName: " & objItem.ComputerName
Wscript.Echo "ConnectionID: " & objItem.ConnectionID
Wscript.Echo "Description: " & objItem.Description
Wscript.Echo "InstallDate: " & objItem.InstallDate
Wscript.Echo "Name: " & objItem.Name
Wscript.Echo "NumberOfFiles: " & objItem.NumberOfFiles
Wscript.Echo "NumberOfUsers: " & objItem.NumberOfUsers
Wscript.Echo "ShareName: " & objItem.ShareName
Wscript.Echo "Status: " & objItem.Status
Wscript.Echo "UserName: " & objItem.UserName
Next


参考:
net sessionコマンド - 管理者必見! ネットワーク・コマンド集:ITpro net session コマンドの説明です。
[WMI] 共有フォルダのセッションを取得するサンプル: Win32_ServerConnection - WMI Sample
[WMI] Win32_ServerConnection クラス - WMI Library
@IT:Win32 APIやDLL関数を呼び出すには? Win32APIを呼び出す方法です。
[PR]
by jehoshaphat | 2011-06-07 00:04 | .Net開発 | Trackback | Comments(0)
(.Net)特定のターミナルサーバ上のユーザにメッセージを送信する
.Netアプリケーションから任意のWindowsユーザにメッセージを送信する方法です。

他のコンピュータやターミナルサービス利用中のユーザにメッセージを送信するには msg コマンドが使えます。
(学生時代 net send というコマンドで他のWindowsPCにメッセージを送れることを知ってよく遊んでたんですが、今は net send コマンドの代わり msg コマンドを使うようです。Vista以降は net send コマンド自体が使えないようですし。。)

ちなみに、Windows7やVistaではターミナルサービスセッションに対するリモートからのRPC接続が無効に設定されているようなので、普通にはメッセージを送れないようです。
クライアントPCのレジストリで HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server キーの AllowRemoteRPC の値を 1 にするとRPC接続が有効にされれるようです。このあたりの詳しい話は【Management】Msgコマンドを使用すると「セッション名の取得エラー5」 - フィールドSEあがりの安納ですが参考になります。


msg コマンドは、ユーザ or セッションID or セッション名 でどのセッションに送信するかや、どのコンピュータに送信するかなどが指定できます。(どのコンピュータに送るかについて、ホスト名でなくてもIPアドレスでもOKでした。)
ただし、そのセッションに対するアクセス権を持っていないと「エラー [5]:アクセスが拒否されました。」となってしまいます。
まぁドメイン管理者権限なら大抵大丈夫なはずです。

msg コマンドでデフォルトの設定で送信すると、どうやら60秒間しかメッセージが表示されませんでした。ということで、1分では足りない場合は /time:秒数 で指定した方がよさそうです。

また、msgコマンドでアクセス権等不足してたり送った先のユーザが既にログアウトしてたりとエラーがあった場合ですが、どうやらエラーがあってもプロセスの戻り値は0のようです。
なので、エラーがあったかどうかは標準エラー出力からとらないとわかりません。
で、.Netでコンソールアプリケーションの標準出力や標準エラー出力を取得する方法ですが、Process.StandardError プロパティで標準エラー出力、Process.StandardOutput プロパティで標準出力の値が取れるようです。
ただし、Process.StandardError プロパティにアクセスするには ProcessStartInfo.RedirectStandardError を true に、Process.StandardOutput プロパティにアクセスするには、ProcessStartInfo.RedirectStandardOutput を true にしないといけないようです。
(上記の方法は同期式の方法と呼ばれており、処理中プロセスの標準出力を取ろうとしたりするとデッドロックになってしまう可能性があるようです。その場合はイベントハンドラを設定する非同期式を使うことが勧められています。)


ということで下記が.Netからmsgコマンドをキックしてメッセージを送信するコードです。コマンドプロンプトウィンドウは表示しないようにしてます。(C#)

/// <summary>
/// メッセージを送信する
/// </summary>
/// <param name="strUserName">ユーザ名</param>
/// <param name="strHostName">コンピュータ名orIPアドレス</param>
/// <param name="strMsg">メッセージ</param>
/// <param name="iDispTime">メッセージ表示秒数</param>
/// <returns>エラーメッセージ(nullの場合は正常)</returns>
public static string SendMessage(string strUserName, string strHostName, string strMsg, int iDispTime){
//プロセスオブジェクト生成
Process proc = new Process();
// 起動するアプリケーションを設定する
proc.StartInfo.FileName = "msg.exe";
//引数指定
proc.StartInfo.Arguments = string.Format("{0} /server:{1} /time:{2} {3} ", strUserName, strHostName, iDispTime.ToString() , strMsg);
// ウィンドウを作成しないかどうかを設定する (初期値 false)trueにするとウィンドウ表示なしとなる
proc.StartInfo.CreateNoWindow = true;
// シェルを使用するかどうか設定する (初期値 true)falseにするとコンソールウィンドウが表示なしとなる
proc.StartInfo.UseShellExecute = false;
// 起動できなかった時にエラーダイアログを表示するかどうかを設定する (初期値 false)
proc.StartInfo.ErrorDialog = false;
//標準エラー出力を取得できるようにする。
proc.StartInfo.RedirectStandardError = true;
 
// 起動する
proc.Start();
//起動したプロセスが終了するまで待つ。1秒のみ
proc.WaitForExit(1000);
 
//標準エラー出力取得
if (proc.HasExited){
string strErr = proc.StandardError.ReadToEnd();
strErr = strErr.Replace("\r\n", "");
if (String.IsNullOrEmpty(strErr)){
return null; //正常終了
}
return strErr; //異常終了 エラーメッセージ返す
}
return "タイムアウトしました。";
}

ただドメインのAdministrator以外だとアクセス権の問題で送信できないことがあるので、管理者ユーザ以外で使う場合は(.Net)別ユーザでプロセスを起動するで書いたようにAdministrator権限でmsgをキックするようにするといいと思います。

参考:
msg - Windowsコマンド集:ITpro
[Terminal Server コマンド] MSG
【Windows7/Vista】ポップアップメッセージを送りたい(net send あらため msg.exe) - フィールドSEあがりの安納です - Site Home - TechNet Blogs
コマンド・プロンプトでファイル共有を管理する - @IT コマンドで管理する場合の情報です。
DOS「メッセージ送信」コマンド(Hishidama's Windows DOS command "msg" Memo)
[.NET] コンソールアプリケーションの標準入出力をリダイレクトする - あおいるかの倉庫
[PR]
by jehoshaphat | 2011-06-06 23:42 | .Net開発 | Trackback | Comments(0)