ブログトップ 記事一覧 ログイン 無料ブログ開設

子持ちししゃもといっしょ RSSフィード

2012-09-22

他のPCのMACアドレスを取得する方法

先日、なんとなく横に置いてあるPC(ディスプレイ無し)のMACアドレスを知りたくなりました。

ただ、リモートデスクトップをつなぐのもめんどくさいなあと思って以下の方法で調べました。


1. コマンドプロンプトを立ち上げる

f:id:itotto:20120923012029p:image


2. 以下のコマンドを実行する

f:id:itotto:20120923012407p:image

C:\Users\itotto>ping -n 1 192.168.11.1 > NUL

C:\Users\itotto>arp -a

インターフェイス: 192.168.11.5 --- 0xa
  インターネット アドレス      物理アドレス      種類
  192.168.11.1          00-0d-0b-b5-2e-e8     動的
  192.168.11.255        ff-ff-ff-ff-ff-ff     静的
  224.0.0.22            01-00-5e-00-00-16     静的
  224.0.0.251           01-00-5e-00-00-fb     静的
  224.0.0.252           01-00-5e-00-00-fc     静的
  239.255.255.250       01-00-5e-7f-ff-fa     静的
  255.255.255.255       ff-ff-ff-ff-ff-ff     静的

今回は192.168.11.1というIPアドレスを対象にしていますが、ここは好きに変えてください。


この物理アドレスという列がMACアドレスの値です。


これはあらためて書くまでもないのですがMACアドレスはあくまで同一ネットワーク内の通信で使われるものであり、ネットワークの異なるコンピューターのMACアドレスは通常の通信を行う上では必要となることはありません(つまり取れない)。専用のアプリケーションを使えばできますが、通常はルーター(ゲートウェイ)のMACまでしか取れないのでその点はご注意ください。


おまけ

以下のバッチファイルを使えば対話式に求めることができます(テストしてないのでエラーが出たら教えてください)。


@ECHO OFF

ECHO IPアドレスを入力してください
SET /PIPADDR=IF "%IPADDR%"=="" GOTO ERRFIN

:: いったんクリアする場合はこちら
arp -d && PING -n %IPADDR% > NUL && arp -a | FIND /I "%IPADDR%"
IF %ERRORLEVEL%.==1. GOTO NOTFIND
GOTO FIN


:ERRFIN
ECHO IPアドレスが入力されていません
GOTO FIN

:NOTFIN
ECHO %IPADDR%のMACアドレスは見つかりませんでした
GOTO FIN

:FIN
ECHO [なにかキーを押してください]
PAUSE > NUL

2010-04-16

ローカルユーザーのパスワードをランダムに変更する

Administratorsに所属しているあるローカルユーザーのパスワードをランダムに変更したいという、もう二度となさそうな要望があったのでバッチファイルを作成しました。

ローカルユーザーの登録がない場合は作成してからAdministratorsに追加登録しています。

これ自体は役に立ちませんが、RANDOM環境変数の使い方やNET USERコマンドあたりはよく使うので備忘としてあげておきます。


@ECHO OFF

:: [1] 変更したいユーザー名
SET CUSERNAME=XXXXXX

:: [2] 変更後のパスワード[初期値は乱数を利用]
SET CPASSWORD=%RANDOM%%RANDOM%%RANDOM%%RANDOM%

:: * ユーザーのパスワードを変更
NET USER %CUSERNAME% %CPASSWORD%

:: * ユーザーが存在しなくてエラーになったときは追加
IF %ERRORLEVEL%.==2. (
  NET USER %CUSERNAME% %CPASSWORD% /Add
  NET LOCALGROUP Administrators /Add %CUSERNAME%
)

:: * 結果表示
ECHO ユーザー[%CUSERNAME%]を追加しました
ECHO パスワードは %CPASSWORD% です
ECHO.
ECHO 何かキーを押してください
PAUSE > NUL

2010-04-15

SQLServerをインストールしたサーバーのコンピューター名を変更する

あまりないケースですが、SQLServerをサーバーにインストールしたあとにサーバーの名前を変更しなければならないときがあります。

このとき、サーバーの名前を変更した後に何もしなくてもほとんどのクエリーが動くのですが、内部的にもっているsysservers(SQLServer2005以降ではsys.servers)を新しいサーバー名で書き換えないとDB-Library経由でのアクセスや、リンククエリーでエラーになる場合があります。


その書き換えのためのバッチファイルを作成しました。


@ECHO OFF
::-------------------------------
:: サーバー名情報
::-------------------------------
:: 新しいサーバー名
SET SVNAME=%COMPUTERNAME%

:: 旧サーバー名[適当に変更してください]
SET OLDSVNAME=OLDServerName

::-------------------------------
:: 接続情報
::-------------------------------
:: 接続ユーザー名[適当に変更してください]
SET CONNUSER=xxxx

:: 接続パスワード[適当に変更してください]
SET CONNPASS=Pa$$w0rd


:: SQLServerの起動
NET START MSSQLSERVER

:: 旧サーバー情報削除
OSQL -U%CONNUSER% -P%CONNPASS% -Q"sp_dropserver '%OLDSVNAME%'"

:: 新サーバー情報登録
OSQL -U%CONNUSER% -P%CONNPASS% -Q"sp_addserver '%SVNAME%','local'"


:: SQLServerの再起動
NET STOP MSSQLSERVER

:: 3秒停止
PING -n 3 127.0.0.1 > NUL 

NET START MSSQLSERVER

接続ユーザー名とパスワードを適宜変更するか、もしくはOSQLで接続する部分をWindows認証に変えて(-U -Pの代わりに-Eを指定)ください。

2009-10-30

データベースの復旧モデルを一括で取得する

先日、SQLServerの特定インスタンス内にある全データベースの復旧モデルを調べるバッチファイルを作成しました。

そのうち無くしてしまいそうなので、ここに残しておきます。

@ECHO OFF

:: 接続先を指定[既定値は自分自身]
SET SERVERNAME=%COMPUTERNAME%

ECHO 接続先を指定してください
SET /PSERVERNAME=[初期値:%SERVERNAME%]>

:: ログファイルのパスを設定
SET LOGFILE="%~dp0RecovList_%SERVERNAME%.log"

:: クエリを実行
OSQL -Usa -P.SSVPASS -dmaster -S%SERVERNAME% -w2000 -Q"sp_MSforeachdb @command1=\"SELECT '?' as DBNAME, Convert(varchar(20),DATABASEPROPERTYEX(N'?', N'RECOVERY')) as 復旧モード\"" > %LOGFILE%

:: ログファイルを表示
NOTEPAD %LOGFILE%

以前、他の記事でも書きましたが、sp_MSforeachdbを使うのがポイントです。

あとはDATABASEPROPERTYEXでデータベースの設定を取得出来るというのもとても大事です。

2009-08-02

全データベースの全テーブルのインデックスの再構築をする

実はいまものすごくハマっている(と言ってものめり込んでいるという意味ではなくてトラブル真っ只中という意味の方です...)ことがありまして、それがプログラムの処理がものすごく遅くなる現象です。気付いたら遅延どころかタイムアウトも出ちゃう有様でして、いったいどうやって対処しようかと悩んでいるところですが、そんなわたしに「DBのデータが断片化してるんじゃね?」というとてもありがたいヒントをくれた方がおりまして、じゃあためしにデータベースのすべてのテーブルのインデックスを再構築してみようということになりました。


テストや実環境で使用しているデータベースサーバーはSQLServer2005 Standard Editionです。

未確認ですが、たぶんSQLServer2008でも大丈夫のはずです。ただし2000の場合はシステムテーブルの表記(sys.databases → sysdatabasesなど)を一部書き換える必要がありますのでご注意ください。

# 詳しくは「続きを読む」を選択してください



まずは本当に断片化しているかどうかを確認したいのですが、これを実現する方法について調べてみました。すると、DBCC SHOWCONTIGというステートメントを実行すると接続中のデータベースの全テーブルの断片化状況をレポートしてくれることが分かりました。

というわけで、これをすべてのデータベースへ接続して実行すればよいわけでして、これをTransactSQLだけでやろうという場合には、sys.databasesからデータベース名を取得してカーソルに入れて、あとはひとつずつ処理すればやりたいことは出来そうです。


とは言え、さすがにこれはちょっといけてないなというのもそのとおりでして、もっと便利な方法がないか調べてみました。

すると非常に便利な隠しプロシージャ(アンドキュメンテッドプロシージャと呼ばれていることが多いようですが)があることを知りました。


sp_MSforeachdbsp_MSforeachtableがそのプロシージャです。


sp_MSforeachdbは現在接続しているインスタンス内のすべてのデータベースを列挙して処理をするためのプロシージャです。

また、sp_MSforeachtableは現在接続しているデータベースの各ユーザーテーブルを列挙して処理をするためのプロシージャです。


どういう動作をするのか、簡単なテストクエリーを書いてテストしてみました。


まずはsp_MSforeachdbから。


[クエリー]

sp_MSforeachdb @command1="SELECT '?'"

[結果]

f:id:itotto:20090730132310p:image


すべてのデータベース名が表示されます。SELECT '?'がこのデータベース名の表示を行っている部分なのですが、見てのとおり、?がデータベース名に置き換わっています。つまり、このプロシージャを使うことで、?でそれぞれのデータベース名を動的に取得しながら処理を行うことが可能となるのです。


動かした際の感覚としては、カーソルを使う場合よりも高速な気がします。

カーソルを使った場合と処理時間を実測して比較しようかと考えたのですが、面倒なのでひとまず置いておくことにします。



続いてsp_MSforeachtableについて。


[クエリー]

sp_MSforeachtable @command1="SELECT '?'"

[結果]

f:id:itotto:20090730132311p:image


接続中のデータベース内にあるすべてのユーザーテーブルの名前が表示されます。SELECT '?'でテーブル名の表示が出来るようです。sp_MSforeachdbでは?がデータベース名に置き換わっていましたが、sp_MSforeachtableでは?がテーブル名に置き換わっています。

よって、このプロシージャを使えば、?でテーブル名を動的に取得しながら処理を行うことが可能となることがわかりました。


ちなみに@command1は128文字以上は入らないようでして、そう考えるとあまり無茶な処理は出来ません。


とりあえず便利なプロシージャがあるということがわかりましたが、これを使って断片化状況を調査するクエリを作成してみます。


[クエリー]

sp_MSforeachtable @command1="dbcc showcontig"

[実行結果]

DBCC SHOWCONTIG により、'spt_fallback_db' テーブルがスキャンされています...

テーブル: 'spt_fallback_db' (117575457)、インデックス ID: 0、データベース ID: 1

TABLE レベルのスキャンが実行されました。

  • スキャンされたページ数.....................: 0
  • スキャンされたエクステント数...............: 0
  • エクステントの切り替え回数.................: 0
  • エクステントごとの平均ページ数.............: 0.0
  • スキャン密度 [最善値:実際値]...............: 100.00% [0:0]
  • エクステント スキャンの断片化 .............: 0.00%
  • ページごとの平均空きバイト数...............: 0.0
  • 平均ページ密度 (全体) .....................: 0.00%

DBCC SHOWCONTIG により、'spt_fallback_dev' テーブルがスキャンされています...

テーブル: 'spt_fallback_dev' (133575514)、インデックス ID: 0、データベース ID: 1

TABLE レベルのスキャンが実行されました。

  • スキャンされたページ数.....................: 0
  • スキャンされたエクステント数...............: 0
  • エクステントの切り替え回数.................: 0
  • エクステントごとの平均ページ数.............: 0.0
  • スキャン密度 [最善値:実際値]...............: 100.00% [0:0]
  • エクステント スキャンの断片化 .............: 0.00%
  • ページごとの平均空きバイト数...............: 0.0
  • 平均ページ密度 (全体) .....................: 0.00%

こうやって実際にどのくらい断片化されているのかを確認すれば、デフラグすべきかどうかが分かります。



さて。では本題。

実際にインデックスの再構築をする方法について調べてみると、方法がいくつか見つかりました。


    1. DBCC DBREINDEX を使う
    2. DBCC INDEXDEFRAG を使う
    3. ALTER INDEX を使う

わざわざ別のステートメントにしているということは違いがあると言うことですからそれぞれについて調べてみたいのですが、このうち、ALTER INDEXについては今回は調査を保留します。理由としては再構築する対象として、テーブルではなくインデックスを直接指定する必要があるようでして、そうだとするとテーブル名から対象となるインデックス名を取得する処理を行う必要が出てくるために処理が煩雑になってしまうのです。

# sp_MSforeachindexというのを使えば出来そうですが、それはさすがに...


と言うわけで、今回はDBCCを使った処理について調べてみます。


1. DBCC DBREINDEX

MSDNへのリンク


DBCC DBREINDEXは指定されたテーブル内のインデックスを再構築してくれるステートメントです。


このステートメントが優れている点はテーブル名だけを指定することでそのテーブルに付いているすべてのインデックスを自動で再構築してくれるためです。つまりインデックス名を個別に指定する必要はありません。


またこのステートメントがひとつのトランザクションになりますがそれはとても使い勝手がよいです。個別にインデックスを再作成する場合は、意識的に"インデックスの削除"と"インデックスの作成"をひとつのトランザクションとしてまとめる必要がありますが、それを使う側が意識する必要がないのです。


そしてもう一点このステートメントのメリットとしては、一つ一つを再作成するよりも最適化されるそうです(上記MSDNでMicrosoftが自己申告しているのを信じれば..ですが)。


逆にデメリットとしては実行中はロックがかけられてしまうという点です。

非クラスタ化インデックスの場合は共有ロックがかけられるため、更新処理が一切出来なくなってSELECT処理のみ可能となります。

さらにクラスタ化インデックスの場合は排他ロックまでかけられてしまうので照会を含む一切のアクセスが出来なくなります。

そのため、オフラインで再構築処理(実行中の業務利用は不可)を行う必要があります。


加えて対象テーブルを指定する場合、テーブル名を所有者やデータベース名で修飾することは出来ないために該当のデータベースへ

直接接続して実施する必要があります。これは処理を行う上で使いにくいため、デメリットとして挙げておきます。


そして一番残念なのは、DBREINDEXは2008の次のバージョンで削除される予定だということです。

とても有効なステートメントなのですが、互換性を考えると選択肢としては難しいですね...。


2. DBCC INDEXDEFRAG

MSDNへのリンク


DBCC INDEXDEFRAGはDBREINDEX同様、指定されたテーブルまたはビューのインデックスの断片化を解消してくれます。再構築ではなく、あくまで解消だという点が強調されていました。


DEREINDEXと比較して、このステートメントのメリットとして挙げられるのはオンライン処理が可能であるという点です。

つまりロックがかけられて処理が出来なくなるということはないということですが、これは非常に大きいです。


また、デフラグ対象のテーブルのあるデータベースもステートメントで指定できるため、プログラムから呼ぶ際には比較的使いやすいという点もメリットとして挙げられます。加えて処理の進捗も定期的に出力してくれるそうでしてこれは非常に配慮あるプロシージャであると感じました。


逆にデメリットとしては、断片化が進んでいる場合には処理には処理に時間がかかるという点です。また再構築に比べて単にページを並び替えるだけなので効果が薄いという情報もありました。口先だけはいいことをいうけど中身の伴わない優男みたいなプロシージャです。


DBREINDEXを使うのか、それともINDEXDEFRAGを使うのかと悩んだのですが、大して違いはありませんのでとりあえずどっちも使えるようにどちらも作成しました。と言っても数行書き換えるだけなのですが...。



[DBREINDEX用のクエリファイル]

-- ファイル名: QueryCreate.sql

DECLARE @dbname varchar(50)
DECLARE @dbid   varchar(10)


-- データベース名一覧取得のカーソル
DECLARE dbname_cs CURSOR FOR
 SELECT name, Convert(varchar(10),database_id) FROM sys.databases WHERE name not in ('master', 'msdb', 'tempdb', 'model') ORDER BY 1

OPEN dbname_cs

-- データを取得
FETCH NEXT FROM dbname_cs INTO @dbname, @dbid
WHILE @@FETCH_STATUS = 0 BEGIN
  print 'use ' + @dbname
  print 'print ''-----------------------------------------------------'''
  print 'print ''○ データベース名 = ' + @dbname + ''''
  print 'print ''-----------------------------------------------------'''
  print 'declare @tblName' + @dbid + ' varchar(20)'
  print 'declare tblcs' + @dbid + ' cursor for select name from sys.tables'
  print 'OPEN tblcs' + @dbid
  print 'fetch next from tblcs' + @dbid + ' into @tblName' + @dbid
  print 'while @@FETCH_STATUS = 0 begin'
  print '  if exists (select ''x'' from sys.objects where name = @tblName' + @dbid+ ') begin'
  print '    print @tblName' + @dbid
  print '    exec (''dbcc dbreindex(''' + @tblName + ''')'')'
  print '  end'
  print '  SELECT GetDate() as [時刻]'
  print '  fetch next from tblcs' + @dbid + ' into @tblName' + @dbid
  print 'end'
  print 'close tblcs' + @dbid
  print 'deallocate tblcs' + @dbid
  print ''

  -- データを取得
  FETCH NEXT FROM dbname_cs INTO @dbname, @dbid
END

CLOSE dbname_cs
DEALLOCATE dbname_cs

[INDEXDEFRAG用のクエリファイル]

-- ファイル名: QueryCreate.sql

DECLARE @dbname varchar(50)
DECLARE @dbid   varchar(10)


-- データベース名一覧取得のカーソル
DECLARE dbname_cs CURSOR FOR
 SELECT name, Convert(varchar(10),database_id) FROM sys.databases WHERE name not in ('master', 'msdb', 'tempdb', 'model') ORDER BY 1

OPEN dbname_cs

-- データを取得
FETCH NEXT FROM dbname_cs INTO @dbname, @dbid
WHILE @@FETCH_STATUS = 0 BEGIN
  print 'use ' + @dbname
  print 'print ''-----------------------------------------------------'''
  print 'print ''○ データベース名 = ' + @dbname + ''''
  print 'print ''-----------------------------------------------------'''
  print 'declare @tblName' + @dbid + ' varchar(20)'
  print 'declare tblcs' + @dbid + ' cursor for select name from sys.tables'
  print 'OPEN tblcs' + @dbid
  print 'fetch next from tblcs' + @dbid + ' into @tblName' + @dbid
  print 'while @@FETCH_STATUS = 0 begin'
  print '  if exists (select ''x'' from sys.objects where name = @tblName' + @dbid+ ') begin'
  print '    print @tblName' + @dbid
  print '    exec (''dbcc INDEXDEFRAG(' + @dbname + ','' + @tblName' + @dbid + ' + '')'' )'
  print '  end'
  print '  SELECT GetDate() as [時刻]'
  print '  fetch next from tblcs' + @dbid + ' into @tblName' + @dbid
  print 'end'
  print 'close tblcs' + @dbid
  print 'deallocate tblcs' + @dbid
  print ''

  -- データを取得
  FETCH NEXT FROM dbname_cs INTO @dbname, @dbid
END

CLOSE dbname_cs
DEALLOCATE dbname_cs

あれ?と思った方もいると思いますが、実はこのクエリーを実行してもデフラグはされません。このクエリーを実行するとそのサーバー環境でデフラグをするためにクエリーが生成されます。

直接実行すればいいのに、なぜこんなまどろっこしいことをするのかと言うと、理由が二つあります。


ひとつ目の理由はクエリーファイルを残しておくことで、最終的にどういう処理をしたのかを明確に残しておくことが出来るということです。

どんな処理でもそうですが、実行した場合にはそのログを残すべきです。が、どのくらい詳細なログを残せばいいのかというのはいつも頭を悩ませるところです。その点、実施した処理そのものの命令文を残しておくというのはログという観点からもとてもよいことだと言えます。


ふたつ目の理由はデバッグがしやすいということです。動的にSQLを生成して実行する場合、どういうクエリーが実行されているのかを見るためには実行前にそれを表示する必要があります。わざわざ表示と実行を切り替えるくらいだったら、毎回クエリーを出力しておいてそれを実行した方がよいではないかとうわけです。

加えて、処理の一部をちょっとだけ変えたい場合にはその作成されたクエリーファイルを直接編集再利用することが出来るのも大きなメリットといえます。


あとは最近あまり聞かないのですが、まったく同じクエリーでも動的クエリーと静的クエリーでは生成される実行プランが異なるということがあったと記憶しています(execが主流だった前のことですが)。sp_execsql経由であれば問題ないはずですが、動的クエリーよりは静的の方が実行プランに与える影響は少ないんじゃないかという心理的な部分も影響しています。

まあ、これは古い意見だと思います。


最後にこのクエリーを呼び出すバッチファイルを作れば完成です。

実際に使う場合には接続ユーザー名やパスワードを適宜設定してから実行してください。


@ECHO OFF

:: ログファイルの絶対パス
SET LOGFILE="%~dp0%COMPUTERNAME%_DbRebuild.log"

:: 使用するクエリファイルの絶対パス
SET QUERYFILE="%~dp0QueryCreate.sql"

:: 自動生成するクエリファイルの絶対パス
SET TMPQUERYFILE="%TEMP%\tmp_%COMPUTERNAME%.sql"

:: 接続ユーザー名
SET CONNUSER=sa

:: 接続ユーザーパスワード
SET CONNPW=xxxxxxxxx

:: ログファイルの削除
IF EXIST %LOGFILE% (
  DEL %LOGFILE%
)

:: 一時クエリファイルの削除
IF EXIST %TMPQUERYFILE% (
  DEL %TMPQUERYFILE%
)

:: クエリの生成処理
OSQL -U%CONNUSER% -P%CONNPW% -dmaster -i%QUERYFILE% -n -w2000 > %LOGFILE%
IF %ERRORLEVEL%.==0. (
  COPY %LOGFILE% %TMPQUERYFILE% /Y > NUL

  ECHO ------------------------------------------------- > %LOGFILE%
  ECHO ○ データベース再構築処理 [%DATE% - %TIME%]       >> %LOGFILE%
  ECHO  作業者:%USERNAME%%USERDOMAIN%                >> %LOGFILE%
  ECHO ------------------------------------------------- >> %LOGFILE%
  ECHO. >> %LOGFILE%
  OSQL -U%CONNUSER% -P%CONNPW% -dmaster -i%TMPQUERYFILE% -n -w2000 >> %LOGFILE%

  NOTEPAD %LOGFILE%
  GOTO FINISH
)

ECHO 処理は失敗しました[エラーコード=%ERRORLEVEL%]
PAUSE > NUL

:FINISH

データ量にもよりますが、結構処理には時間がかかりそうです。

ものすごく時間がかかる場合にはINDEXDEFRAGを使い、そうでない場合にはDBREINDEXを使う方がよいのかも知れません。

2009-06-26

DOSのコマンドだけでは出来ないことをバッチファイルで実行する方法


最近バッチファイルを使う機会がめっきりと減っていますが、それでもコンパイラいらずというメリットはとても大きくてとても重宝しています。例えば、サーバーで作業をしている時にどうしても明日の朝に一度だけ実行したい処理が出てきたときなどは、その処理をバッチファイルで書いてタスクに登録しておけば対応することが可能となります。


さて。そんなバッチファイルの一番の問題点は、OSごとに使えるコマンドが違うという点にあります。

例えば上で例にあげたタスクの登録ですが、これは「schtasks」というコマンドを使うことでコマンドからタスクの登録が可能となります。ですが、実際にこのコマンドが使えるのはWindows2003からであり、2000以前の場合にはこのコマンドは使えません。古い環境では機能の劣るatコマンドを使うしかないのです。


また、Windows XP以降ではプロセスの一覧を取得するコマンドとして「tasklist」というコマンドが利用できますが、2000以前のコマンドプロンプトではプロセス一覧を取ることが出来ないのです。こちらについては代替案すらありません。


こういったOSごとの差異をバッチファイルだけ吸収するのはとても面倒ですが、だからといって専用のコマンドをCやC++で書くのも余計面倒です。もしくは動作させる対象の環境を限定してしまえば細かいことは考慮しなくてもいいのですが、使えないケースがあるというのは意外に不便です。

ではどうすればよいのかとあれこれ考えて出した結論は、WSHを生成して実行すればいいんじゃね?ということです。

具体的にどうやるのかということについてはプロセスの一覧取得のバッチファイルを例に説明します。


@ECHO OFF

SET SCRFILE="%TEMP%\tmptasklist.vbs"

ECHO Wscript.echo "イメージ名                     PID セッション名     セッション# メモリ使用量" > %SCRFILE%
ECHO Wscript.echo "========================= ======== ================ =========== ============" >> %SCRFILE%
ECHO. >> %SCRFILE%
ECHO Set objWMIProcess = GetObject("winmgmts:").InstancesOf("Win32_Process") >> %SCRFILE%
ECHO For Each myProcess In objWMIProcess >> %SCRFILE%
ECHO         If myProcess.SessionId = 0 Then >> %SCRFILE%
ECHO 		SSNAME = "Services" >> %SCRFILE%
ECHO 	Else >> %SCRFILE%
ECHO 		SSNAME = "Console" >> %SCRFILE%
ECHO 	End If >> %SCRFILE%
ECHO 	Wscript.echo myProcess.Name ^& vbTab ^& myProcess.ProcessId ^& vbTab ^& SSNAME ^& vbTab ^& myProcess.SessionId ^& vbTab ^& myProcess.WorkingSetSize >> %SCRFILE%
ECHO Next >> %SCRFILE%

:: デバッグ用
:: NOTEPAD %SCRFILE%

CSCRIPT %SCRFILE%


これはプロセスの一覧を表示させるためのVBScriptである以下のスクリプトを生成して実行しているだけなのですが、出力結果はおおむねTASKLISTと同じになります。


Wscript.echo "イメージ名                     PID セッション名     セッション# メモリ使用量" 
Wscript.echo "========================= ======== ================ =========== ============" 
 
Set objWMIProcess = GetObject("winmgmts:").InstancesOf("Win32_Process") 
For Each myProcess In objWMIProcess 
        If myProcess.SessionId = 0 Then 
		SSNAME = "Services" 
	Else 
		SSNAME = "Console" 
	End If 
	Wscript.echo myProcess.Name & vbTab & myProcess.ProcessId & vbTab & SSNAME & vbTab & myProcess.SessionId & vbTab & myProcess.WorkingSetSize 
Next 

ポイントというほどのことは特にないのですが、注意した方がよい点として特殊文字のエスケープが挙げられます。

上記のバッチファイルでは、 & という文字を表示するために ^& という表記をしています。これは & が直前のコマンドが正常実行された場合に次のコマンドを実行するという特殊な記号を表しており、これをエスケープするために^という文字を使っています。

同様に | もエスケープが必要な文字です。


ちなみに変数を表す % をエスケープする場合には % を重ねることでエスケープされます。


データの表示に限って言えば、上記の例に挙げたWMIを使うことでやりたいことのほぼ大半は出来ます。

スクリプトでスクリプトを生成するというのは常套手段ではありますが、バッチファイルでもこの手法はお勧めですし、今回のようにファイルに一度保存しておくことでデバッグもやりやすくなりますのでこの流れは覚えておくと使いまわせます。


[追記]

書き忘れたのですが、注意事項としてはWSHのバージョンが違うOSや、そもそもWSHが使えないOSというのもありますので、そういったOSまで含めて考える場合にはさすがにWin32アプリケーションを作るのがよいと思います。

2009-05-09

「and の使い方が間違っています」というエラー

アクセスログを見ていたらこんな検索ワードで飛んできた人がいました。


f:id:itotto:20090509215558p:image


「andの使い方が間違っています」というのはWidows 2000とWindows XPでバッチファイルを実行した時によく出るエラーです。

実際にエラーの出たバッチファイルを見てみても「and」という単語はたぶん見つからないはずなので、「じゃあいったい何が原因なんだろう...」と頭を悩ませることになることが多いと思います。


とりあえず答えを書くと、このエラーはデスクトップなど、ユーザのプロファイル以下にバッチファイルを配置して実行していることに起因して発生するエラーであり、エラーに出ている「and」というのはWindows 2000とWindows XPでデフォルトのユーザプロファイルフォルダになっている「C:\Documents and Settings」というフォルダの一部です。


例えば。

バッチファイルにはさまざまなパラメータを利用することが出来ます。例を挙げると %~dp0 と書くと、バッチファイルのあるフォルダの絶対パスを表します。詳しくはコマンドプロンプトで CALL /? と打っていただくと出てきますが、起動時のコマンドの一番最初(つまり呼び出しているバッチファイルの絶対パス)を表す %0 という変数に ~dp という文字列を加えることでこの変数をドライブ+パス名だけに展開することが出来ます。


上記のような方法で「バッチファイルの絶対パス」を取得して利用しようとした場合、ただしく引用句(ダブルクォーテーション)を付けたり削除したりと気を遣わないといけないケースがあります。それは「バッチファイルが空白を含むパスに配置されているケース」です。

空白を含むパスを引用句なしでそのままコマンドに渡してしまうと、パスが分割されて実行コマンドに渡されてしまい個別に実行されてしまいます。


そんなわけで対処方法は大きく2つ。


    1. 引用句を付けるなどしてバッチファイルを直す
    2. C:\temp とか空白を含まないパス配下にバッチファイルを置いて実行する

自分で作って配布しているものであれば1番の対応が好ましいですが、とりあえず動かしたいのであれば2番の対応で十分だと思います。


こんなしょうもないエラーの出るバッチファイルを書いてしまうことはとても恥ずかしいことなので作って配布する場合には要注意です。