「ほっ」と。キャンペーン
<   2009年 04月 ( 14 )   > この月の画像一覧
(.Net)Imageオブジェクトとバイト配列相互変換
(.Net,SQL)SQL Server 2005に画像データ(バイナリ)を保存したいの追加情報です。
この記事の最後のほうで、一時ファイルを使わずに直に Image とバイト配列に変換できる方法を探す と言ってましたが、@IT バイト配列→画像オブジェクト/画像オブジェクト→バイト配列の変換を行うには?で載ってました。

ということで下記のように(@ITのコードそのままですが)したら、DBへの保存とDBからの読み出しともにOKでした。
※picPohto1 はPictureBoxコントロール。
'保存
'Imageをバイナリ(Byte配列)にコンバートする
Dim imgconv As New ImageConverter()
Dim byt1 As Byte() = CType(imgconv.ConvertTo(Me.picPhoto1.Image, GetType(Byte())), Byte())
'DataSetを使ってデータ挿入
Dim adp As DataSet1TableAdapters.CustomerTableAdapter = New DataSet1TableAdapters.CustomerTableAdapter();
adp.InsertQuery(1, "テスト太郎", byt1);
 
'読み込み
Dim tbl As New DataSet1.CustomerDataTable()
adp.FillByID(tbl, 1)
'DBにはバイナリ(Byte配列)なので、Imageにコンバートする
Dim imgconv2 As New ImageConverter()
Me.picPhoto1.Image = CType(imgconv2.ConvertFrom(tbl.Rows(0).Item("img")), Image)

[PR]
by jehoshaphat | 2009-04-29 01:24 | .Net開発 | Trackback | Comments(0)
(.Net).Netアプリケーションから Windows タスクスケジューラを操作する(@IT参考コード編)
(.Net).Netアプリケーションから Windows タスクスケジューラを操作するの記事補足です。@ITの会議室に上がってたコードを VB.Net に変換したものです。
詳しくは上記記事を参考に。

ふと疑問に思ったのが、DLL を読んでるところです。
VB.Net から DLL の関数見るのって、Declare を使うんじゃなかったでしたっけ?
と思い見つけたのが【VB.NET】Windows APIの利用
なるほど。Declare 使うのと、DLLImport属性使う方法があるっぽいです。

このサンプルでは、ole32.dll 内の CoCreateInstance メソッドを使って、クラスIDを渡し、ITaskScheduler オブジェクトを生成してるんですね。

(文字数制限のためハイライトは無しです)

Imports System.Runtime.InteropServices

Public Class Class2

' CLSID, IIDの定数定義
Private Shared CLSID_CTaskScheduler As New Guid("{148BD52A-A2AB-11CE-B11F-00AA00530503}")
Private Shared CLSID_CTask As New Guid("{148BD520-A2AB-11CE-B11F-00AA00530503}")
Private Shared IID_ITaskScheduler As New Guid("{148BD527-A2AB-11CE-B11F-00AA00530503}")
Private Shared IID_ITask As New Guid("{148BD524-A2AB-11CE-B11F-00AA00530503}")

'''
''' CLSCTX列挙子の定義
'''

'''
Public Enum CLSCTX
CLSCTX_INPROC_SERVER = &H1
CLSCTX_INPROC_HANDLER = &H2
CLSCTX_LOCAL_SERVER = &H4
CLSCTX_INPROC_SERVER16 = &H8
CLSCTX_REMOTE_SERVER = &H10
CLSCTX_INPROC_HANDLER16 = &H20
CLSCTX_RESERVED1 = &H40
CLSCTX_RESERVED2 = &H80
CLSCTX_RESERVED3 = &H100
CLSCTX_RESERVED4 = &H200
CLSCTX_NO_CODE_DOWNLOAD = &H400
CLSCTX_RESERVED5 = &H800
CLSCTX_NO_CUSTOM_MARSHAL = &H1000
CLSCTX_ENABLE_CODE_DOWNLOAD = &H2000
CLSCTX_NO_FAILURE_LOG = &H4000
CLSCTX_DISABLE_AAA = &H8000
CLSCTX_ENABLE_AAA = &H10000
CLSCTX_FROM_DEFAULT_CONTEXT = &H20000
CLSCTX_INPROC = CLSCTX_INPROC_SERVER Or CLSCTX_INPROC_HANDLER
CLSCTX_SERVER = CLSCTX_INPROC_SERVER Or CLSCTX_LOCAL_SERVER Or CLSCTX_REMOTE_SERVER
CLSCTX_ALL = CLSCTX_SERVER Or CLSCTX_INPROC_HANDLER
End Enum

'''
''' TASK_TRIGGER_TYPE列挙子の定義
'''

'''
Public Enum TASK_TRIGGER_TYPE
TASK_TIME_TRIGGER_ONCE = 0
TASK_TIME_TRIGGER_DAILY = 1
TASK_TIME_TRIGGER_WEEKLY = 2
TASK_TIME_TRIGGER_MONTHLYDATE = 3
TASK_TIME_TRIGGER_MONTHLYDOW = 4
TASK_EVENT_TRIGGER_ON_IDLE = 5
TASK_EVENT_TRIGGER_AT_SYSTEMSTART = 6
TASK_EVENT_TRIGGER_AT_LOGON = 7
End Enum

' ITaskSchedulerインターフェイスの定義
InterfaceType(ComInterfaceType.InterfaceIsIUnknown), _
Guid("148BD527-A2AB-11CE-B11F-00AA00530503")> _
Interface ITaskScheduler
Sub SetTargetComputer() ' dummy
Sub GetTargetComputer() ' dummy
Sub Enum_() ' dummy
Sub Activate() ' dummy
Sub Delete() ' dummy
Function NewWorkItem(<[In](), MarshalAs(UnmanagedType.LPWStr)> ByVal pwszTaskName As String, _
<[In](), MarshalAs(UnmanagedType.LPStruct)> ByVal rclsid As Guid, _
<[In](), MarshalAs(UnmanagedType.LPStruct)> ByVal riid As Guid _
) As Object

Sub AddWorkItem() ' dummy
Sub IsOfType() ' dummy
End Interface

' ITaskインターフェイスの定義
InterfaceType(ComInterfaceType.InterfaceIsIUnknown), _
Guid("148BD524-A2AB-11CE-B11F-00AA00530503")> _
Interface ITask
' IScheduledWorkItemインターフェイスメソッド
Function CreateTrigger(ByRef iNewTrigger As System.UInt16 _
) As Object
Sub DeleteTrigger() ' dummy
Sub GetTriggerCount() ' dummy
Sub GetTrigger() ' dummy
Sub GetTriggerString() ' dummy
Sub GetRunTimes() ' dummy
Sub GetNextRunTime() ' dummy
Sub SetIdleWait() ' dummy
Sub GetIdleWait() ' dummy
Sub Run() ' dummy
Sub Terminate() ' dummy
Sub EditWorkItem() ' dummy
Sub GetMostRecentRunTime() ' dummy
Sub GetStatus() ' dummy
Sub GetExitCode() ' dummy
Sub SetComment() ' dummy
Sub GetComment() ' dummy
Sub SetCreator() ' dummy
Sub GetCreator() ' dummy
Sub SetWorkItemData() ' dummy
Sub GetWorkItemData() ' dummy
Sub SetErrorRetryCount() ' dummy
Sub GetErrorRetryCount() ' dummy
Sub SetErrorRetryInterval() ' dummy
Sub GetErrorRetryInterval() ' dummy
Sub SetFlags() ' dummy
Sub GetFlags() ' dummy
Sub SetAccountInformation(<[In](), MarshalAs(UnmanagedType.LPWStr)> ByVal pwszAccountName As String, _
<[In](), MarshalAs(UnmanagedType.LPWStr)> ByVal pwszPassword As String)
Sub GetAccountInformation() ' dummy
' ITaskインターフェイスメソッド
Sub SetApplicationName(<[In](), MarshalAs(UnmanagedType.LPWStr)> ByVal pwszApplicationName As String)
Sub GetApplicationName() ' dummy
Sub SetParameters() ' dummy
Sub GetParameters() ' dummy
Sub SetWorkingDirectory() ' dummy
Sub GetWorkingDirectory() ' dummy
Sub SetPriority() ' dummy
Sub GetPriority() ' dummy
Sub SetTaskFlags() ' dummy
Sub GetTaskFlags() ' dummy
Sub SetMaxRunTime() ' dummy
Sub GetMaxRunTime() ' dummy
End Interface

' ITaskTriggerインターフェイスの定義
InterfaceType(ComInterfaceType.InterfaceIsIUnknown), _
Guid("148BD52B-A2AB-11CE-B11F-00AA00530503")> _
Interface ITaskTrigger
Sub SetTrigger(ByRef pTrigger As TASK_TRIGGER)
Sub GetTrigger() ' dummy
Sub GetTriggerString() ' dummy
End Interface 'ITaskTrigger

' CoCreateInstanceのインポート
_
Public Shared Function CoCreateInstance( _
<[In](), MarshalAs(UnmanagedType.LPStruct)> ByVal rclsid As Guid, _
ByVal pUnkOuter As Object, _
ByVal dwClsContext As CLSCTX, _
<[In](), MarshalAs(UnmanagedType.LPStruct)> ByVal riid As Guid _
) As Object
End Function

' TASK_TRIGGER構造体の定義
_
Public Structure TASK_TRIGGER
Public cbTriggerSize As System.UInt16
Public Reserved1 As System.UInt16
Public wBeginYear As System.UInt16
Public wBeginMonth As System.UInt16
Public wBeginDay As System.UInt16
Public wEndYear As System.UInt16
Public wEndMonth As System.UInt16
Public wEndDay As System.UInt16
Public wStartHour As System.UInt16
Public wStartMinute As System.UInt16
Public MinutesDuration As System.UInt32
Public MinutesInterval As System.UInt32
Public rgFlags As System.UInt32
Public TriggerType As TASK_TRIGGER_TYPE
'TRIGGER_TYPE_UNION Type;
Public Type1 As System.UInt16
Public Type2 As System.UInt16
Public Type3 As System.UInt16
Public TypePadding As System.UInt16
Public Reserved2 As System.UInt16
Public wRandomMinutesInterval As System.UInt16
End Structure

Public Sub Main()
Try
' タスクスケジューラ オブジェクトを取得する
Dim iTaskScheduler As ITaskScheduler = CType(CoCreateInstance(CLSID_CTaskScheduler, Nothing, CLSCTX.CLSCTX_INPROC_SERVER, IID_ITaskScheduler), ITaskScheduler)
' 新しいタスクを作成する
Dim iTask As ITask = CType(iTaskScheduler.NewWorkItem("TestTask", CLSID_CTask, IID_ITask), ITask)
' ITaskSchedulerインターフェイスを解放する
Marshal.ReleaseComObject(iTaskScheduler)
' 起動するアプリケーション名を設定する
iTask.SetApplicationName("notepad.exe")
' アカウント名、パスワードを設定する
iTask.SetAccountInformation("testuser", "passwd")
' トリガーを作成する
Dim iNewTrigger As System.UInt16
Dim iTaskTrigger As ITaskTrigger = CType(iTask.CreateTrigger(iNewTrigger), ITaskTrigger)
' スケジュールを設定する
Dim pTrigger As New TASK_TRIGGER()
pTrigger.cbTriggerSize = CType(Marshal.SizeOf(pTrigger), System.UInt16)
pTrigger.wBeginYear = 2009
pTrigger.wBeginMonth = 5
pTrigger.wBeginDay = 1
pTrigger.wStartHour = 0
pTrigger.wStartMinute = 0
pTrigger.TriggerType = TASK_TRIGGER_TYPE.TASK_TIME_TRIGGER_ONCE
iTaskTrigger.SetTrigger(pTrigger)
' ITaskTriggerインターフェイスを解放する
Marshal.ReleaseComObject(iTaskTrigger)
' IPersistFileインターフェイスを取得する
Dim pPersistFile As IntPtr = IntPtr.Zero
Dim IID_IPersistFile As Guid = Marshal.GenerateGuidForType(GetType(UCOMIPersistFile))
Marshal.QueryInterface(Marshal.GetIUnknownForObject(iTask), IID_IPersistFile, pPersistFile)
' ITaskインターフェイスを解放する
Marshal.ReleaseComObject(iTask)
' タスクをディスクへ保存する
Dim iPersistFile As UCOMIPersistFile = CType(Marshal.GetObjectForIUnknown(pPersistFile), UCOMIPersistFile)
iPersistFile.Save(Nothing, True)
' IPersistFileインターフェイスを解放する
Marshal.ReleaseComObject(iPersistFile)
Catch e As Exception
' 例外発生時にメッセージとスタックとレースを出力する
Console.WriteLine(e.Message)
Console.WriteLine(e.StackTrace)
System.Windows.Forms.MessageBox.Show(e.Message)
End Try
End Sub
End Class

[PR]
by jehoshaphat | 2009-04-28 23:56 | .Net開発 | Trackback | Comments(0)
(.Net).Netアプリケーションから Windows タスクスケジューラを操作する
(SQL Server 2005) Express Edition は SQL Agent job スケジューリングサービス がない!?で書いたように Express Edition はエージェントサービスがないため、自動バックアップするためにアプリ側から Windows のタスクスケジューラにバックアップ処理を登録して行おうと考えました。

これが想像以上に悪戦苦闘しました。単に.Netアプリケーションからタスクを登録したいだけなのに。。。

今回の要件はこんな感じです。
・プログラム側からタスクの登録、削除、一覧取得ができる。
・Windows 2000(Client),Windows Server 2003(Server)以上で動くこと。(もちろんXPのHomeEditionでも)

まず、Windows タスクスケジューラにタスクを登録する方法はいくつかあるようです。
1.GUIから操作する
XPだとコンパネのタスク,Vistaだと管理ツールのタスクスケジューラから操作する方法です。今回の要件はアプリケーション側から操作するの却下。

2.at.exe コマンドを使う
Windows NT 時代からある古いコマンドです。ただこれは互換性のためだけに残されているコマンドで非常に使い勝手が悪いです。@IT タスク・スケジューラをコマンド・プロンプトから制御するでも「Windows XP以降のOSでは、at.exeは利用せず、必ずこちらを利用するべきである」とあるので、却下。(一応 Vista でも at コマンド存在してました。)

3.schtasks.exe コマンドを使う
このコマンドラインタスク管理ツールを使うとかなり柔軟にできるのでいいんですが、一つ問題が。。XP以降でしか使えないことです。さらにXPでも Home Editon はこのコマンドが存在しません。なんでって感じですがこの方法も惜しみながら却下。

4.WMI で Win32_ScheduledJob クラスを使う
この Win32_ScheduledJob は at.exe 使っており、結局2.と同じなので却下。
WMIでタスク管理する方法はTechNet:スクリプトを使用してタスクのスケジュールを管理することはできますか参照。

5.Win32 API NetScheduleJob系 を使う
一応 API で NetScheduleJobAdd , NetScheduleJobDel , NetScheduleJobEnum , NetScheduleJobGetInfo が用意されてるみたいですが、これも少し調べてみるとあんまり使い勝手良くないみたいですね。というよりこれが at.exe や Win32_ScheduledJob のコアになってるんじゃないでしょうか?(詳しく調査してないんで分かりませんが。。) ま、そういうわけで却下。

6.COM ITaskSchedulerを使う
ITaskScheduler を使うと大抵のことはできそうな感じです。今回の要件も満たせるので結局この方法で行くことにしました。しかし、COMがよくわかってない故に苦戦しました。。

ちなみに Vista 以降はタスクスケジューラのバージョンが2.0になったためより COM も ITaskScheduler ではなく ITaskService を使って操作するスタイルになったようです。(
また、WMI でも Schedule.Service たるクラスが用意されてスクリプトで簡単にタスク操作できるようになってるっぽいですね。
Vista でスクリプトからタスク操作する方法はTechNet Windows Vista でスケジュールされたタスクを実行する方法はありますかを参照。)


まず参考にさせていただいたのは@IT BBS【C#】C#アプリからによるタスク登録の自動化についてです。


どうやらタスクスケジューラ操作関連の COM は C:\Windows\System32\mstask.dll に格納されてるっぽいです。

さて、実は不肖ながら3流PGは COM 技術についてほとんど無知です。
概念はちょこっとはかじってますが、いざ COM を作ったり使うことはほとんどしたことがありません。

ただ以前 DirectShow を.Netから使うときに COM の ActiveMovieControl を参照設定から利用したときはありました。(ActiveMovieControlでDirectShow。)

で、今回も試しに ActiveMovieControl の時と同じ手法(参照の追加でCOMタブ)でCOMを使おうとしましたが、一覧に出てきません。
まさに、上記 @IT BBS の質問者の状態です。

まず、この点(つまり .Net からどうすれば COM が見えるのか)を調べてみました。
参考にしたのは、DirectShowの時も読んだMSDN:第 7 章 「相互運用パフォーマンスの向上」 と、MSDN:COM 相互運用性 - 第 1 部 : C# クライアント チュートリアルです。

ActiveMovieControl を使った時は、Visual Studio で参照設定すれば勝手に Interop.QuartzTypeLib.dll という DLL ができて、これを操作すれば COM 簡単に扱えました。
COM にはタイプライブラリというインターフェイスの定義を格納したものがあるらしく、Visual Studio はこのタイプライブラリを指定すると自動的に.NET Framework メタデータに変換し、どのマネージ言語からでも呼び出すことができるマネージ ラッパーを作成してくれるようです。
この マネージラッパーは Visual Studio を使わなくても NET の SDK に付属する TlbImp.exe (C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\TlbImp.exe) を使っても作成できるようです。
試しに ActiveMovieControl のDLL(c:\Windows\System32\quartz.dll) を TlbImp を使ってマネージラッパーに変換してみました。

C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin>TlbImp.exe c:\Windows\System32\quartz.dll
Microsoft (R) .NET Framework Type Library to Assembly Converter 3.5.30729.1
Copyright (C) Microsoft Corporation. All rights reserved.

Type library imported to QuartzTypeLib.dll

これで QuartzTypeLib.dll が生成されました。

ところが、どの COM もタイプライブラリを持っているというわけではないようです。
上記の "MSDN:COM 相互運用性 - 第 1 部 " によると、「COM 定義を C# に変換する方法としては TlbImp が適していますが、常に TlbImp を使用できるわけではありません。たとえば、COM 定義の typelib が存在しない場合や、TlbImp が typelib の定義を処理できない場合は TlbImp を使用できません。このような場合には、C# 属性を使用して C# ソース コード内で COM 定義を手動で定義します。C# ソース マップを作成すると、C# ソース コードをコンパイルするだけでマネージ ラッパーを生成できます。」とありますしね。

で、今回の使う予定の ITaskScheduler の COM はタイプライブラリを持っていません。(タスクスケジューラ2.0以降(Vista以降)では、タスク操作のCOMへのタイプライブラリが提供されてるみたいです。Visual Studio の参照追加でも「TaskScheduler1.1 タイプ ライブラリ」と出てきます。詳しくはMSDNマガジン:タスク スケジューラ 2.0。これ使えば簡単なんでしょうが、なぜXP以前にはないのか。。。)
つまり、タイプライブラリが本来持っているインターフェイスを自身で定義してやらないといけないわけです。そして、便利なマネージラッパークラスももちろん作ってくれません。


話はそれますが、COM自体について。。
COM はインタフェースを介して、呼び出し元プログラムと通信を行うようです。
あんま理解してませんが、詳しくはCOMの基礎がや@IT:COM+に見る分散オブジェクトの特徴 分かりやすいです。
また、まだ最初しか読んでませんが、MSDN:COM プログラミングの基本 (上),COM プログラミングの基本 (中),COM プログラミングの基本 (下)も参考になりそうです。
で、COM は クラスID、インターフェイスID が決まっていて(中身はGUID)、たいていレジストリに GUID に対応する DLL へのパスが格納されてるみたいですね。(GUID から実際のファイルパスへの解決は API レベル(例えば CoCreateInstance() 等)でやってるくれるみたいなので開発者は意識しなくてよいようです。)
そういえば、レジストリにクラスIDみたいなものあったなーと思い見てみました。
例えば今回使うタスクスケジューラのCOMが入っている mstask.dll はレジストリ上は下記の場所に定義されてました。
HKEY_CLASSES_ROOT\CLSID\{148BD52A-A2AB-11CE-B11F-00AA00530503}\InProcServer32


さて、そろそろ本題復帰。

参考にしている@IT BBS【C#】C#アプリからによるタスク登録の自動化についてで、ありがいたいことにタスクを登録する C# のコードが紹介されてました。


今回のプロジェクトは VB.Net だったので上で紹介されたコードを VB.Net に変換してみました。

(.Net).Netアプリケーションから Windows タスクスケジューラを操作する(@IT参考コード編)にコードはあげています。

ただ、タスク登録だけしかできません。なぜなら、インターフェイスのメソッドの最低限の登録以外はすべてダミーメソッドだからです。
ということは、残りのメソッドやインターフェイスの定義を書かないといけません。

定義自体は探せば結構あるんですよね。
1つはMSDN:Task Scheduler 1.0 Interfacesはインターフェイスの定義+リファレンスが載ってます。ただし、英語だけで日本語の情報はないようで。。。
また、@IT であったPINVOKE.NETでもITaskSchedulerのインターフェイスは載せられてました。(このサイトはWin32 APIやアンマネージAPIをマネージコードから呼び出すためのPInvokeの記述を探すことのできるサイトとして、結構有名らしいです。知りませんでした。。)
さらに、Visual C/C++開発環境か Windows SDK 入ってると mstask.dll のヘッダファイル "MSTask.h"(パスは自分の環境だとC:\Program Files\Microsoft SDKs\Windows\v6.0A\Include)でもインターフェイスの定義分かります。ただ、量が多いし、C/C++になれないと大変。。

今回の要件を満たすためのコードを書くだけでも結構な数の定義を書かないといけません。
時間もないし、メソッドの引数の型変換のマーシャリングや.Netでの属性がいまいちわかってないので自力はなかばあきらめモードに...

てなわけで他力本願全開でググっているとこのような記事見つけました。
Windowsのタスクスケジューラをプログラムから設定する。この追記部分のリンク先のページから "Download demo project" でタスクスケジューラ操作用ライブラリがダウンロードできました。
しかし、言語が C++ && 英語(コメントがね)。今回は使えませんが、C++ やるときにはいいかもしれません。


さらにいろいろググってるついに見つけました。

番外編入れると12回という長期連載の ITaskScheduler もろもろ実装方法の記事です。
COM クライアント実装の道程 for TaskScheduler その1
COM クライアント実装の道程 for TaskScheduler その2
COM クライアント実装の道程 for TaskScheduler その番外編1 ~アンマネージドメモリへの手抜き
アンマネージドメモリへの手抜き VB.NET編
COM クライアント実装の道程 for TaskScheduler その3
COM クライアント実装の道程 for TaskScheduler その4
COM クライアント実装の道程 for TaskScheduler その番外編2 ~ COM オブジェクトと GC とファイナライザ
COM クライアント実装の道程 for TaskScheduler その5
COM クライアント実装の道程 for TaskScheduler その6
COM クライアント実装の道程 for TaskScheduler その7
COM クライアント実装の道程 for TaskScheduler その8
COM クライアント実装の道程 for TaskScheduler その最終回
かなり長いですが、読むとなるほどというところが多いですね。
最終回ではタスク操作をライブラリ化して VS プロジェクトまで公開しているので、非常に助かります。
最初からのこの記事みつけとば半日苦労せずにすみました。。。

余談ですが、タスクスケジューラで登録したタスクって、%sytemroot%¥Tasks フォルダにファイルとして登録されるんですね。
恥ずかしながら知りませんでした。。
[PR]
by jehoshaphat | 2009-04-28 23:45 | .Net開発 | Trackback | Comments(0)
(SQL Server 2005)データベースの内容をダンプする bcp ツール
SQL Server で MySQL のダンプのようにデータを含むクエリを作ってくれる簡単な方法はないのかと思って調査してる時に見つけたツールです。

この bcp ツールというのは SQL Server に付属しており、テーブルデータのエクスポートやインポートが簡単にできるようです。
といっても、データがタブ区切りでエクスポートもしくはインポートできるだけなので、MySQL のようなクエリ付ではありません。
(しかも、テーブル単位でしかできないのがちょっと不便ですが。。)

bcp コマンドの詳細はMSDN:bcp ユーティリティを参照。

コマンドのオプションで -c,-w と -n,-N があるんですが、-c,-w だとテキスト形式で落とすので可読性はありますが、そのデータをインポートするときに問題が発生するかもしれません。-n,-N はネイティブデータ型を使用するので吐き出したデータをインポートする可能性があるならこっちの方が無難でしょう。
-c と -w , -n と -N の違いはUnicode文字を使用するかどうかなので、-N でネイティブ型のUnicode文字使用するのがお勧めです。

簡単な使い方は【Microsoft SQL Server】 BCPユーティリティの使用法[SQLServer]BCPユーティリティ とはで紹介されてます。

インポートの際には一旦今のデータを消しておかないと一意制約かけてる列ではエラーになってしまいます。

一応下記のような感じで軽く試してみました。

ネイティブデータ型(Unicode)で出力
C:\Users\TestUser>bcp TestDB.dbo.CustomerTable out c:\testest.txt -N -S hostname\SQL2005DEV -T

コピーを開始しています...

15 行コピーされました。
ネットワーク パケット サイズ (バイト): 4096
クロック タイム (ミリ秒) 合計 : 1 平均 : (15000.00 行/秒)

テキスト型で出力
C:\Users\TestUser>bcp TestDB.dbo.CustomerTable out c:\testest_char.txt -c -S hostname\SQL2005DEV -T

コピーを開始しています...

15 行コピーされました。
ネットワーク パケット サイズ (バイト): 4096
クロック タイム (ミリ秒) 合計 : 1 平均 : (15000.00 行/秒)


出力したデータをインポート(データを消さずにインポートしようとしたのでユニーク制約に引っ掛かってしまった)
C:\Users\TestUser>bcp TestDB.dbo.CustomerTable in c:\testest.txt -n -S hostname\SQL2005DEV -U sa -P passwd -E

コピーを開始しています...
SQLState = 23000, NativeError = 2627
Error = [Microsoft][SQL Native Client][SQL Server]制約 'aaaaaCustomerTable_PK' の PRIMARY KEY 違反。オブジェクト 'dbo.CustomerTable' には重複したキー
を挿入できません。
SQLState = 01000, NativeError = 3621
Warning = [Microsoft][SQL Native Client][SQL Server]ステートメントは終了されました。

BCP コピー in が失敗しました

一旦テーブルのデータを消してからインポートするとうまくいった。
C:\Users\TestUser>bcp TestDB.dbo.CustomerTable in c:\testest.txt -n -S hostname\SQL2005DEV -U sa -P passwd -E

コピーを開始しています...

15 行コピーされました。
ネットワーク パケット サイズ (バイト): 4096
クロック タイム (ミリ秒) 合計 : 1 平均 : (15000.00 行/秒)


-c(テキスト形式)でエクスポートしたときはこんなファイルになります。

31真実 太郎シンジツ タロウ19760520
62真実 花子シンジツ ハナコ19811114
106岸川 秀樹キシカワ ヒデキ19690619
117鈴本 伊一郎スズモト イイチロウ19560907


あんまり使うことはないかもしれませんが、覚えておくと便利なツールかもしれません。
[PR]
by jehoshaphat | 2009-04-28 01:51 | SQL | Trackback | Comments(0)
(.Net)SQL Server でバックアップ時(復元時)の進行状況を取得しプログレスバーに表示する(ソース編)
(.Net)SQL Server でバックアップ時(復元時)の進行状況を取得しプログレスバーに表示する(説明編)の続きとういより、文字数オーバーで載せれなかったコードです。
(なお、プログレスバーを貼り付けているフォームクラスのコードは面倒なので掲載してません。
クラス名は Progress で、プログレスバーコントロール名は ProgressBar です。)


Public Class TestBackup
 
''' <summary>
''' SQL Server のバックアップを行う(バックアップファイル名は日時.bak)
''' エラー時、成功時ともに、イベントビュアーにログを残す
''' </summary>
''' <returns>True:正常 False:異常</returns>
Public Function BackupNow() As Boolean
'コネクションオブジェクト作成
Dim conn As SqlConnection = Me.GetDbConnection()
'プログレスバー表示クラス生成
prg = New ProgressBarControl()
 
Try
conn.Open()
'コマンドオブジェクト生成
Dim cmd As New SqlCommand()
cmd.Connection = conn
'STATS = 1 なので1秒おきに進捗率を通知
cmd.CommandText = "BACKUP DATABASE [testdatabase] TO DISK = N'C:\test.bak' WITH NOFORMAT, INIT, NAME = N'テスト', SKIP, NOREWIND, NOUNLOAD, STATS = 1"
'復元時だとこういうコードになります。
'cmd.CommandText = "RESTORE DATABASE [testdatabase] FROM DISK = N'C:\test.bak' WITH FILE = 1, NOUNLOAD, REPLACE , STATS = 1"
'これをTrueしないとInfoMessageがまとめて発行されてしまう(ただし、TrueにするとSQLクエリ実行中に例外発生してもスローしないので注意!)
conn.FireInfoMessageEventOnUserErrors = True
'進捗情報取得のためのイベントハンドラを追加
AddHandler conn.InfoMessage, New SqlInfoMessageEventHandler(AddressOf SqlConnect_InfoMassage)
'プログレスバー表示(別スレッド)
prg.Show()
'バックアップクエリ実行
cmd.ExecuteNonQuery()
Catch ex As Exception
'エラー処理のコードがここに入る(ログに残すとかメッセージボックスとか)
Return False
Finally
conn.Close()
If prg IsNot Nothing Then
'プログレスバー表示用別スレッドを停止。プレブレスバー表示フォームを閉じる。
prg.Close()
End If
End Try
Return True
End Function
 
 
''' <summary>
''' 進行状況ダイアログのプログレスバーに進捗を表示する。
''' SqlConnection.InfoMessageのイベントハンドラ。
''' </summary>
Private Sub SqlConnect_InfoMassage(ByVal sender As Object, ByVal e As System.Data.SqlClient.SqlInfoMessageEventArgs)
Dim conn As SqlConnection = DirectCast(sender, SqlConnection)
For Each sqlErr As SqlError In e.Errors
'エラーの重要度が10より大きいなら例外発生させ処理停止
If sqlErr.Class > 10 Then
'Falseにすることで、次回より例外がスローされるようになる
conn.FireInfoMessageEventOnUserErrors = False
'エラーを ApplicationExceptionとして例外生成(しかし、この例外はスローされないっぽい。この次の例外からスローされる)
Dim ex As New ApplicationException(sqlErr.ToString())
'この下のThrowでは実際にはThrowされないようなので、ここでイベントビュアーにエラー出力
'エラー処理のコードがここに入る(ログに残すとかメッセージボックスとか)
Throw ex
End If
Next

'メッセージを分解し、intに変換
Dim idx As Integer = e.Message.LastIndexOf(" パーセント処理されました。")
If idx < 1 Then
Return
End If
'パーセント
Dim intPer As Integer
Try
intPer = CType(e.Message.Substring(0, idx), Integer)
Catch ex As Exception
Return
End Try
'プログレスバー表示更新
If prg IsNot Nothing Then
prg.ValuePercent = intPer
End If
End Sub
End Class
 
 
''' <summary>
''' プログレスバーフォームを別スレッドで表示さすためのラッパクラス
''' </summary>
Public Class ProgressBarControl
 
'進行状況表示クラス(このクラスのコードは省略。ただフォームにプログレスバーを乗せてるだけ)
Private m_statusForm As Progress
'現在のプログレスバーの値
Private m_ValuePercent As Integer
 
''' <summary>
''' プログレスバーの値を設定、取得するプロパティ
''' </summary>
Public Property ValuePercent() As Integer
Get
Return m_ValuePercent
End Get
Set(ByVal value As Integer)
m_ValuePercent = value
If m_statusForm IsNot Nothing Then
'm_statusFormのインスタンスを持っているスレッドで、SetProgressValuePercentメソッドを処理させる
m_statusForm.Invoke(New MethodInvoker(AddressOf SetProgressValuePercent))
End If
End Set
End Property
 
''' <summary>
''' プログレスバーを閉じる。(と同時にプログレスバー表示スレッドも自然と消える)
''' </summary>
Public Sub Close()
If m_statusForm IsNot Nothing Then
'm_statusFormのインスタンスを持っているスレッドで、ProgressCloseメソッドを処理させる
m_statusForm.Invoke(New MethodInvoker(AddressOf ProgressClose))
End If
End Sub
 
 
''' <summary>
''' プログレスバーを表示する。プログレスバー表示クラスを別スレッドで作成し、表示。
''' </summary>
Public Sub Show()
Dim thread As Threading.Thread = New Threading.Thread(AddressOf Run)
thread.Start()
End Sub
 
''' <summary>
''' 別スレッドで動くメソッド。プログレスバー表示クラスのインスタンスを作成し、ShowDialog する。
''' </summary>
Private Sub Run()
m_statusForm = New Progress()
m_statusForm.ShowDialog()
m_statusForm.Dispose()
End Sub
 
 
''' <summary>
''' プログレスバーの値を更新する(MethodInvoker デリゲートから呼ばれる)
''' </summary>
Private Sub SetProgressValuePercent()
m_statusForm.ProgressBar.Value = m_ValuePercent
End Sub
 
''' <summary>
''' プログレスバーを閉じる(MethodInvoker デリゲートから呼ばれる)
''' </summary>
Private Sub ProgressClose()
m_statusForm.Close()
End Sub
 
End Class

[PR]
by jehoshaphat | 2009-04-28 01:36 | .Net開発 | Trackback | Comments(0)
(.Net)SQL Server でバックアップ時(復元時)の進行状況を取得しプログレスバーに表示する(説明編)
Widowsアプリケーションから SQL Server のバックアップ、リストア時に進捗をパーセント表示する方法です。

参考にさせてもらったのはSQLServer2005のバックアップ中に進行状況を得るには です。
ここを見ると、BACKUP DATABASE クエリに WITH STATS を加えると OleDbConnection.InfoMessage で進捗率を取得できるということでした。
SqlConnection クラスにも InfoMessage イベントはあったので簡単に実装できるかと思いきやここからが苦戦しました。

まず、SqlConnection.InfoMessage イベントで WITH STATS で指定した間隔毎にメッセージは習得できるんですが、InfoMessage イベント発生のタイミングが問題でした。どうやら、SqlCommand.ExecuteNonQuery() でクエリを処理し終わった後に InfoMessage イベントが1回だけ走っている用なのです。
クエリ中にたまったメッセージを1回の InfoMessage イベントでコレクションで渡してる感じのようです。

で、これをリアルタイムに(クエリ実行しながら) InfoMessage イベントを起こせないものかと思って MSDN さまよっているとありました。
MSDN:接続イベント (ADO.NET)の「InfoMessages としてのエラー処理」項目で、SqlConnection の FireInfoMessageEventOnUserErrors プロパティを true に設定すると、InfoMessage イベントを発生できる あります。

MSDN:SqlConnection.FireInfoMessageEventOnUserErrors プロパティ のリファレンスを見てみると、「FireInfoMessageEventOnUserErrors を true に設定した場合、それまで例外として処理されていたエラーが InfoMessage イベントとして処理されるようになります。すべてのイベントは即座に発生し、イベント ハンドラによって処理されます。FireInfoMessageEventOnUserErrors を false に設定した場合、InfoMessage イベントはプロシージャの最後で処理されます。 」とありました。

ということで、SqlConnection.FireInfoMessageEventOnUserErrors プロパティを true に指定すると、リアルタイムに InfoMessage イベントが発生し、進捗状況を取得することができるようになりました。

うまくいったかのように思ったんですが、ここでさらに問題が。。
SqlConnection.FireInfoMessageEventOnUserErrors = true にしたことによって、本来クエリ中で発生してた例外がスローしなくなったっぽいのです。
本来 FireInfoMessageEventOnUserErrors プロパティを true にすると例外として処理されていたエラーが InfoMessage イベントとして処理され、例外を発生させなくするものなので、当然と言えば当然。。

しかし、クエリを Try Catch でくくっており例外処理ありきでコーディングしているため、何とかして、例外発生時に Catch で例外を捕捉したいものです。

いろいろ探しまたが、結局スマートな方法はなかったので、強引な方法で行きました。
InfoMessage イベント内でエラーの重大度レベルが一定以上(今回は11以上)なら、FireInfoMessageEventOnUserErrors を false にして ApplicationException を throw するようにしたのです。

あともうひとつスマートではないのは進捗率をプログレスバーに表示するための値ですが、InfoMessage イベントで取得する進行状況のメッセージは "○○ パーセント処理されました。" というものです。仕方なく "パーセント処理されました。" を削除して、int にキャストするようにしました。

とういことで説明が長くまりしたが、コードはこんな感じです。(復元用は省略。バックアップとSQLのクエリが違うだけなので。。)
プログレスバーは別のフォームに貼り付けており、マルチスレッドで値を更新しています。(いつもお世話になっている進行状況ダイアログを表示するを参考にしてます)

と思ってコードを貼り付けようとしたら、Exciteブログが文字数超過エラー。。
仕方なく2ページに分けました。
(.Net)SQL Server でバックアップ時(復元時)の進行状況を取得しプログレスバーに表示する(ソース編)でコードのっけてます。

追記:
SQL Server インストール時に、クライアント ツールをインストールするオプションを選択することで、SMO というSQL Server 管理オブジェクトから容易にバックアップやリストアその他の処理ができるようです。(SQL Server ManagementStudio は内部的に SMO を使ってるっぽいです。)
この記事で取り上げてる進捗率取得ももっと容易にできるようです。
ただ、クライアントツールが入ってないといけないのがちょっとネックですね。
SMO については MSDNマガジン:SQL Server 管理オブジェクトで紹介されてます。(進行状況のサンプルもあります。)
[PR]
by jehoshaphat | 2009-04-28 01:35 | .Net開発 | Trackback | Comments(0)
(SQL Server 2005) Express Edition は SQL Agent job スケジューリングサービス がない!?
無料でつかる SQL Server 2005 Express Editon を使おうという話になりました。
今回の要件はSQL Server 2005の任意のデータベースをバックアップするというものです。
ということで、ManagementStudio からバックアップジョブを登録しようとすると、エラーが。。(バックアップのダイアログで スクリプト → スクリプト操作をジョブに保存 でジョブ登録できます。)
e0091163_9113721.jpg

で、同じ開発PCに入れてた Developer Edition と比較してみました。
例えば Developer Edition だと ManagementStudio で下記のように SQL Server エージェントが見えるんでが、、
e0091163_912149.jpg

Express Edition だとエージェントはありません。
e0091163_914949.jpg


次に、Windows のサービスを見てみたんですが、Developer Edition だとSQL Server Agent (インスタンス名)というのがあるんですが、Express Edition だとないです。

ここで、ようやく Express にはエージェントがサポートされてないのか!? という考えが浮かびました。
で、MSのサイトで確認です。
やはり、「SQL Agent job スケジューリングサービス」のところにチェックありませんでした。

仕方ないので、対応策を考えないといけませんね。

今のところ、Windows のタスクを使おうかと思ってます。
ただ、納品先の素人さんが自分でバックアップのスケジュールを安易に設定できるツールをかまさないといけないので、おそらくアプリケーションからタスクに登録する機能がいるでしょう。
まあ、この作業メモは後ほど。。。
[PR]
by jehoshaphat | 2009-04-21 09:00 | SQL | Trackback | Comments(0)
(.Net,SQL)プログラムからSQL Serverデータベースのバックアップを行う
.Net アプリケーションから SQL Server 2005 のバックアップを行う方法です。

最初は .Net からバッチかDOSコマンドで sqlcmd を使おうと思ってたのですが、よく考えたら ADO.Net 使ってやった方が簡単だということに気付いたんで、その方法でやることにしました。

SQL ServerでのバックアップSQL文は BACKUP DATABASE(ログの場合は BACKUP LOG)で可能なようです。
手で書くのも面倒だったので、ManagementStudioのバックアップである程度指定して、スクリプトボタンからSQL文をゲットしました。
BACKUP コマンドの詳細はMSDN:BACKUP (Transact-SQL)にあります。
あと、TechScrawlさんのバックアップコマンドプログラミング徹底解説→SQL Server バックアップ コマンドで簡単に BACKUP コマンドの説明がわかりやすくされてます。

ADO.Netを使って接続するわけですが、接続文字列にはデータベースの指定(Initial Catalog)はいらないみたいです。
まあ、特定のデータベースの中に対してクエリを発行するわけじゃないので当然と言えば当然ですが。。

で、試しに作ってみたコードがこんな感じです。
'コネクションオブジェクト作成
Dim conn As New SqlConnection()
'接続文字列に Initial Catalog(データベース) は指定しなくてもよい
conn.ConnectionString = "Persist Security Info=False;User ID=sa;Password=testtest;Server=(local)\SQLEXPRESS"
conn.Open()
'コマンドオブジェクト生成
Dim cmd As New SqlCommand()
cmd.Connection = conn
'バックアップするクエリ
cmd.CommandText = "BACKUP DATABASE [test] TO DISK = N'D:\test.bak' WITH NOFORMAT, INIT, NAME = N'test-完全 データベース バックアップ', SKIP, NOREWIND, NOUNLOAD, STATS = 10"
'バックアップクエリ実行
cmd.ExecuteNonQuery()
conn.Close()

今回は接続型(TableAdapterを使わない)でコネクションを張り、クエリを発行してます。
つまり、コマンドが違うだけで、方法は通常の接続型と同じでいけるようです。
[PR]
by jehoshaphat | 2009-04-21 08:58 | SQL | Trackback | Comments(1)
(.Net)設定値をXMLファイルの落とす(アプリケーション構成ファイル,user.configを使わない編)
以前に、(.Net)アプリケーションの設定ファイルを作ろうとしたけど。。。の最後で言及した自作XMLに設定を保存する方法です。クラスをシリアライズして落とす方法もあるんですが、あえてその方法を使わずXMLパーサを利用することに。(実はアプリから XML をパーサで操作するのは今回が初だったりするわけで。。)

.NetにはXMLパーサとして XmlReader,XmlWriter と XmlDocument があるようです。
いったいこの2つは何が違うんじゃということで軽く調査。
前者は SAX (Simple API for XML) と呼ばれるAPIに基づき、後者は DOM (Document Object Model) というAPIに基づくようです。
DOM はWEB系開発でちょこっと触ってるんで(JavaScriptから操作する時に使用)ある程度わかるんですが、SAX というのは初耳です。

詳しくは第10回 XMLプログラミングのためのAPIで両者の違いが説明されてました。
簡単にまとめると DOM はツリー上に情報もつのでランダムアクセス可能、メモリ消費大、すべて読み込んでから処理開始。
SAX はイベントを順次生成という感じなので順次アクセス、メモリ消費小、逐次処理ということだそうです。

結局設定ファイルの更新が発生すること(SAXは更新に弱いらしい(?))や今までの経験値を生かすためと友人の誘惑により DOM 操作にすることにしました。
.Netでは XmlDocument パーサ使うと本当に簡単に XML 操作ができます。

例えば、下記のような XML があって、savedir というタグの値を取りたいときはこう書けます。
<?xml version="1.0" encoding="utf-8" ?>
<settings>
<backup>
<savedir>aaa</savedir>
</backup>
</settings>

Dim  m_XmlDoc As New XmlDocument()
'設定値を読む
m_XmlDoc.Load("test.xml")
'ルートノードの取得
Dim rootElement As XmlElement = m_XmlDoc.DocumentElement
'savedir というタグのノードを検索
Dim nodeList As XmlNodeList = rootElement.GetElementsByTagName("savedir")
'タグの値を出力
For Each node As XmlNode In nodeList
Console.WriteLine(node.InnerText)
Next

しかし、GetElementsByTagName を使う方法だと下記の XML のように別要素の savedir まで取得してしまいます。

<?xml version="1.0" encoding="utf-8" ?>
<settings>
<backup>
<savedir>aaa</savedir>
</backup>
<history>
<savedir>bbbb</savedir>
</history>
</settings>

結果は aaa と bbbb になってしまう。

特定の位置のタグだけ取りたときは SelectNodes と使うとOKみたいですね。
例えば上記の XML で backup 内の savedir の値だけ取りたいときは下記のようにできます。

'ルートノードの取得
Dim rootElement As XmlElement = m_XmlDoc.DocumentElement
'backup の savedir というタグのノードを検索
Dim nodeList As XmlNodeList = rootElement.SelectNodes("//backup/savedir")
For Each node As XmlNode In nodeList
Console.WriteLine(node.InnerText)
Next

この SelectNodes の引数は XPath と呼ばれるもので、より複雑な条件で特定のノードを探すことができるようです。
ちなみに、 SelectSingleNode() を使うと1つだけノードが返るようなので(戻り値の型はXmlNode)、1つしかノードがないことが保証される場合はこっちの方が便利かもしれません。

保存は XmlNode.InnerText を書き換えて、 XmlDocument .Save メソッドで簡単にできます。上書きも勝手にするようです。

他にも XML の名前空間やスキーマ云々等があるようですが、まあ今回の要件ではそこまでいらないのでとりあえずこれで解決とします。

.Net からのDOM操作は連載 .NETで簡単XML 第4回 DOM(Document Object Model)を参考にしてます。

さて、上記でXMLに設定ファイルを書き込むアプリケーションを作って、Visual Studio セットアッププロジェクトでインストーラをつくり、Vista && UACがONの環境で試してみました。

インストール先はインストーラ標準の ProgramFiles 配下です。設定ファイルと exe と同じフォルダに配置します。

そして、アプリケーションを立ち上げ設定を行い保存処理をします。
すると案の定例外が発生。


System.UnauthorizedAccessException: パス 'C:\Program Files\test\xxx\xxx.xml' へのアクセスが拒否されました。
場所 System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
場所 System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy)
場所 System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)
場所 System.Xml.XmlDocument .Save(String filename)
.....

やはり、UAC のせいで書き込み時にアクセス拒否出ました。

exe をコンテキストメニューから「管理者として実行」から行うと問題なく書き込みできます。

今回作成中のアプリケーションは用途として管理者として実行させるものなので、exe を実行したら勝手に管理者権限に昇格させてやるようにしました。(UACのダイアログは毎回出ますが。。)

この昇格をさせるために マニフェストファイル という仕組みがあります。
マニフェストファイルはずっと前の某大手で構内作業者やってた時にVista対応で触ってたんで大体理解できてます。
(本当は、exeやインストーラにもデジタル署名させてあげたいんだけど、現場に証明書発行してもらう金がないみたいなで、未署名のまま。。。)
マニフェストで管理者権限昇格を定義し、exeを起動すると、管理者権限を持つユーザでWindowsにログオンしてばあい、下記のようなダイアログが毎回でます。
e0091163_9434952.jpg

また、一般ユーザでログインしていると下記のようなダイアログになります。
e0091163_9443589.jpg


マニフェストの設定は、ファイル名.exe.manifest という XML で下記のノードを追加してやればOKです。
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
これで管理者権限で実行しようとします。
level の値は AsInvoker:制限された権限で動作可能 ,highestAvailable:ユーザーが持つ制限されない権限が必要,requireAdministrator:管理者権限が必要 となっているようです。 (参考:Vista対応 その2 マニフェストファイルを使用する)

Visual Studio 2008(VB.Net)でUACがらみのマニフェスト設定するには、プロジェクトのプロパティ→UAC設定の表示 を押下するとマニフェストファイルのテンプレート(app.manifest)ができるので、そっから容易に変更できます。
e0091163_9453047.jpg

こんな感じです。(管理者権限で実行を指定)
<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">

<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC マニフェスト オプション
Windows のユーザー アカウント制御のレベルを変更するには、
requestedExecutionLevel ノードを以下のいずれかで置換します。
 
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
 
下位互換性のためにファイルおよびレジストリの仮想化を
利用する場合は、requestedExecutionLevel ノードを削除してください。
-->

<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />

</requestedPrivileges>
</security>
</trustInfo>
</asmv1:assembly>


Visual Studio のプロジェクトのプロパティから精製したマニフェストはアセンブリ埋め込みになるので、外に XML ファイルが作られないようですね。

マニフェストが埋め込まれた exe は下記のように盾のアイコンがつくみたいです。
e0091163_9461216.jpg

[PR]
by jehoshaphat | 2009-04-20 09:56 | .Net開発 | Trackback | Comments(0)
Windows,Linuxの稼動時間を知る
Vistaだとタスクマネージャに稼働時間でてくるのですが、XP の場合コマンドたたかないと出てきません。

Windowsの場合下記コマンドでシステム情報の中に稼働時間が表示されます。

C:\>systeminfo

こんな感じになります。
e0091163_2317289.jpg


また、Linux で下記コマンドで確認できます。

# uptime

こんな感じになります。
e0091163_2314156.jpg

(53日稼動してるっぽいです。そろそろリブートしたほうがいっかな。)
Windowsに比べ余計な情報でないためシンプルです。
その他の項目の意味はシステム管理に役立つコマンド(1:uname,hostname,uptime)を参照。。
[PR]
by jehoshaphat | 2009-04-17 23:17 | 豆知識 | Trackback | Comments(0)