PowerShell でファイル比較

今回のテーマは「2 つのテキストファイルの中身を比較し、一致するかどうか確認する」というものです。

PowerShell にはそのような用途のための Compare-Object というコマンドレットもあるのですが、実行結果の表記にクセがあり、私の求めるものではなかったので自分でスクリプトを書きました。

2 つのテキストファイルに対し、一行目から順番に「文字列が完全一致するか否か」を確認し、各行毎に「True ( 一致する )」ないし「False ( 一致しない )」という結果を出力するスクリプトです。

 

 

まず、E ドライブ直下に基準となる「kijun.txt」と、比較対象となる「hikakutaishou.txt」の 2 つのテキストファイルを作成します。

それぞれの中身は下記の通り。

------ E:¥kijun.txt ココカラ ------------------

aaaaa

bbbbb

ccccc

 

eeeee

------ ココマデ (全 5 行) -----------------------

------ E:¥hikakutaishou.txt ココカラ -------

aaaaa1

bbbbb

ccc

ddddd

------ ココマデ (全 4 行) -----------------------

 

 

2 つのテキストファイルは総行数からして違いますし、文字列が一致している行は 2 行目だけです。

そのことを実行結果 ( kekka.txt ) として得るためのスクリプトは以下の通りです。(PowerShell 2.0 以降で利用可能です。)

=== スクリプト ココカラ ==============================

"【総行数比較】" > e:¥kekka.txt

$NoK = (Get-Content e:¥kijun.txt | measure-object).count

$NoH = (Get-Content e:¥hikakutaishou.txt | measure-object).count

$NoK -eq $NoH >> e:¥kekka.txt

"基    準 : " + $NoK + " 行" >> e:¥kekka.txt

"比較対象 : " + $NoH + " 行" >> e:¥kekka.txt

"+++++++++++++++++" >> e:¥kekka.txt

"【各行比較】" >> e:¥kekka.txt

$LN = 0

while ($LN -le $NoK-1)

{

$K = (Get-Content e:¥kijun.txt)[$LN]

$H = (Get-Content e:¥hikakutaishou.txt)[$LN]

$C = $K -eq $H

$LN2 = $LN+1

$R = $LN2,$C

$R -join " : " >> e:¥kekka.txt

$LN++

}

=== スクリプト ココマデ ==============================

 

 

内容の解説は次回をお待ちください。

Virtual Bytes と PowerShell

Virtual Bytes」とは、パフォーマンスモニタでログを取ることのできるカウンターの一種で、プロセスが使用している仮想アドレス領域の現在の大きさを、バイト数で表します。

(パフォーマンスモニタの詳細な使い方や、「そもそも 仮想アドレス領域 とは何か」という説明は割愛します。)

 

 

今回の主題は、パフォーマンスモニタではなく PowerShell の「Get-Process」コマンドレットを用いて Virtual Bytes の値を確認する際の注意点です。

 

 

コマンドレット例をできるだけシンプルに書くと、こんな感じです。

Get-Process  -name (プロセス名) |  Format-List VirtualMemorySize

※ PowerShell 1.0 でも実行可能です。

※ svchost.exe のように同名で複数存在するプロセスのうち一つだけを指定する場合は、-name ではなく -id によりプロセス固有の「プロセス ID」を指定する必要があります。

 

 

ところが、これでは 64 bit 版の OS に通用しない事があるのです。

値が小さいうちはいいのですが、だいたい 2 GB を超えたぐらいから、なぜかマイナスの値が出てきてしまいます。

(32 bit 版 の OS においては、仮想アドレス領域の上限はデフォルトで「2GB」ですから、そのせいではないかと推測しています。)

 

 

こんな時には、切り出すプロパティを変更します。

Get-Process  -name (プロセス名) |  Format-List VirtualMemorySize64

を実行することで、パフォーマンスモニタ側の「Virtual Bytes」と同じ値を確認出来ます。

名前からして、この「VirtualMemorySize64」こそが 64 bit 環境において確認すべき正しいプロパティなのでしょう。

 

 

 

なお、仮想メモリ領域が徐々に食い潰されていく(パフォーマンスモニタ上で「Virtual Bytes」の値が右肩上がりに上昇していく)様子を確認するには、下記の不具合を利用するという手が有ります。

The handle count and "Virtual Bytes" for the Windows Event Log service keep increasing in Windows Server 2008 or in Windows Vista when you refresh scheduled task history events

 

 

2013 年 12 月 22 日 現在、まだ日本語版のページが作られていません・・・

が、この現象を発生させるのは簡単です。

タスクスケジューラー上で何らかのタスクの「履歴」タブを開き、[最新の情報に更新] ボタンを連打するだけです。

 

 

なお、この不具合は Windows Vista および Windows Server 2008 で発生するものとされ、修正プログラムも出ているのですが、私は Windows 7 でも同様の現象が起きる事を確認しています。

Windows 7  向けの修正プログラムはいつ出るのでしょう・・・

 

PowerShell でドメインユーザー連続作成 ~ aduser 編 ~

前回の While ステートメントに、実際にドメインユーザーを作成するコマンドレット" New-ADUser "を一行書き足すことで、「ドメインユーザー連続作成」ができるようになります。

 

 

ただドメインユーザーアカウントを一つ作成するだけでしたら、" new-aduser <ユーザー名> " だけでも可能です。

そのため、二つの変数 $L = import-csv samplelist.csv , $C = 0 を定義した直後に " new-aduser ($L[$C]).name " を実行した場合、「aaa」をユーザー名とするドメインユーザーが作成されます。

しかし、この状態ではパスワードが設定されていないのは当然として、ユーザーアカウント自体も「無効」となっています。

作成と同時にアカウントを「有効」とするためには、" -Enabled $true " というオプションが必要です。

" net user <ユーザー名> /add /domain " コマンドで作成したユーザーアカウントであれば、特にオプションを付けずとも最初から有効なのですが。

 

 

ドメインユーザーの作成と同時にパスワードも設定するには、" -AccountPassword " というオプションを用います。

しかし、" -AccountPassword ($L[$C]).password " という記述で CSV ファイルからパスワードを読み込もうとしても、失敗してしまいます。

どうやら、「ごく普通の文字列(プレーンテキスト)」はパスワードとして扱えず、「SecureString」というセキュリティ的に保護された文字列である必要があるようです。

CSV から読み込んだ文字列を「SecureString」に変換するためには " ConvertTo-SecureString " というコマンドレットも併用しなくてはいけないので、実際のドメインユーザー作成コマンドレットは下記のようになります。

" New-ADUser ($L[$C]).name -AccountPassword (ConvertTo-SecureString -asplaintext ($L[$C]).password -force) -Enabled $true"

この一行を While ステートメントに組み込んで繰り返し実行することで、CSV に記述された全ユーザーが作成され、同じく CSV に記述されたパスワードを用いてドメイン内のコンピューターにログオン出来るようになります。

 

 

なお、上記コマンドレットにより作成されたドメインユーザーは、「姓」「名」「表示名」「ユーザー ログオン名」が設定されておらず、「ユーザー ログオン名(Windows 2000 より前)」だけが、指定したユーザー名により設定されている状態です。

そのユーザー名でログオンすること自体に支障は無いのですが、必要に応じてオプションを付与し、適宜設定するようにしてください。

 

 

" new-aduser "、" ConvertTo-SecureString " コマンドレットについての詳細は、下記 URL を参照してください。

New-ADUser

http://technet.microsoft.com/ja-jp/library/ee617253(en-us).aspx

 

ConvertTo-SecureString

http://technet.microsoft.com/en-us/library/hh849818.aspx

PowerShell でドメインユーザー連続作成 ~ While 編 ~

前回は、CSV ファイルから任意の位置(列,行)の情報を読み込むところまでやりました。

今回はその「」がテーマです。

" ($L[0]).name " の [0] の部分が「行」を表すわけですが、これを 0 -> 1 -> 2 とカウントアップさせながら、同じ処理(ドメインユーザー作成)を繰り返し行わせる方法を解説します。

 

 

まず、パラメーター(変数)として扱う「行」を、「$C」とします。

最初に " $C = 0 " と定めておけば、 " ($L[$C]).name " の結果として「aaa」を得る事が出来ます。

 

 

次に、$C をカウントアップ(処理を 1 回行うごとに、1 ずつ増加)させながら繰り返し処理するために、While ステートメントを用います。

基本構文は「 While  (繰り返す条件)  {繰り返し実行したいコマンドレット}  」です。

 

 

(繰り返す条件) は、今回の場合「$C が 2 以下の場合」となります。

つまり、CSV ファイルのユーザーリストを 1 行目 -> 2 行目 -> 3 行目 と順に読み込んで処理し、処理するたびにカウントアップした $C が「3」に達したらそれ以降は処理せず、終了します。

これは、" ($C -le 2) " と記述します。

あるいは、「$C が  3 より小さい場合」を意味する " ($C -lt 3) " でも同様の結果となります。

 

 

次に {繰り返し実行したいコマンドレット} ですが、ここは二段構成となります。

一つ目は、本来の目的である「ドメインユーザーの作成」です。これについては次回以降詳述します。

二つ目が、「$C のカウントアップ」です。こちらは、" $C++ " ないし" $C = $C+1 " と記述します。

これにより、ドメインユーザーを一人作り終えた直後に「$C」がカウントアップされるため、次の処理ではCSV ファイルの一つ下の行のデータが読み込まれます。

 

 

これまでのところをまとめると、こうなります。

$L = import-csv samplelist.csv

$C = 0

while ($C -le 2)

{

<ドメインユーザー作成コマンドレット>

$C++

}

 

 

続く

PowerShell でドメインユーザー連続作成 ~ CSV 編 ~

CSV 形式のユーザーリストを元に、PowerShell を用い、Active Directory に複数のユーザーを一度に作成する方法を考えてみました。

なお、全ての操作は、ドメインコントローラー・メンバーサーバーともに Windows Server 2008 R2 SP1 の環境で行っています。

 

 

この作業のためには、PowerShell の3つの機能を組み合わせます。

[1]   CSV 形式のファイルから、指定した位置 (列,行) の情報を読み込む

[2]   [1] で読み込んだ情報を使い、ドメインユーザーを作成する

[3]   CSV の最終行まで、[1],[2] の作業を順番に繰り返す

 

 

第一回の今回は、[1] について記述します。(次回はいったん [3] に飛び、最後に [2] をやる予定です。)

 

 

まず、作成する 3 ユーザーの名前とパスワードのリスト「samplelist.csv」を、下記のように記述します。

※ 先頭行はフィールド名のため、全 4 行となります。

--------<ココカラ>--------

name,password

aaa,bbb

123,456

大畑,fk-01g

--------<ココマデ>--------

 

 

PowerShell で CSV ファイルを読み込む場合、import-csv コマンドレットを用います。

CSV ファイルがカレントディレクトリにある場合は、" import-csv  samplelist.csv " です。

しかし・・・

CSV ファイルを日本語版 Windows の「メモ帳」で作成していた場合、保存時の文字コードは既定で Shift-JIS であるため、PowerShell 上では「大畑」の二文字が文字化けしてしまいます。

この現象は、ファイルを保存する際に文字コードとして Unicode を選択することで回避可能です。

あるいは、 " get-content samplelist.csv > unicode.csv " といったコマンドレットにより、Unicode 形式の別ファイルに変換することも可能です。

 

 

リストの中身全体を正しく読み込めたところで、次は列と行を指定して特定のデータを読み取ります。

まず、リスト全体を読み込むコマンドレットを、変数「$L」として扱えるようにします。

" $L = import-csv  samplelist.csv "

こうすることで、" $L " を実行するだけでリストの中身を読み込めるようになります。

 

 

それから、name 列 のデータ部 1 行目、つまり「aaa」だけを抽出します。

これは " ($L[0]).name " で出来ます。

どうやら、データ部の 1 行目を、PowerShell 的には「0」行目として扱っているようなので、" ($L[1]).name " だと、結果は「123」になってしまいます。

大畑氏のパスワードを表示するには、" ($L[2]).password " です。

 

 

続く

Get-Partition の方が Get-Volume より便利?

"Get-Partition" も、”Get-Vlume“ と同じく、Windows Server 2012 の PowerShell 3.0 から使えるようになったコマンドレットの一つです。

前回の記事では、"Get-Volume" コマンドレットは必ずしも “diskpart” コマンドにおける “list volume” の代用にはならないということを書きましたが、"Get-Partition" コマンドレットであれば代用どころかより豊富な情報が得られることを確認しました。

コンピューター上に存在するパーティションの一覧が、ご丁寧にディスク毎に分かれて表示されますし、その中にはちゃんと「EFI システムパーティション」(100MB)も載っています。

ただし、そのパーティションが「EFI システムパーティション」であるとはどこにも書いてません。「Type」という項目が「System」になっていることが特徴的だと言えますが、それだけです。(Cドライブなど、普通のパーティションは同項目が「Basic」になっています。)

 

 

またさらに、「予約」領域が、"Get-Partition" コマンドレットで表示出来ることを確認しました。「Type」は「Reserved」です。

この領域は、“diskpart” コマンドの "list partition" によって確認出来ますが、“list volume” や、「ディスクの管理」画面では表示されません。

Get-Volume で表示されないパーティションがあった。

Windows Server 2012 の PowerShell 3.0 から使えるようになったコマンドレットの一つに、"Get-Volume" というものが有ります。

特にオプションを付けずにそのまま実行すると、現状でそのコンピューター内に存在するパーティションが全て表示されます。

要するに "diskpart" コマンドにおける "list volume" と同じようなものか・・・ と思っていたのですが、必ずしもそうではないケースもあるということに気づきました。

 

 

普段、各種確認/検証作業は Windows Server 2012 の Hyper-V 上にある仮想マシンで行っているのですが、ふとした思いつきで、ホスト側でもこの "Get-Volume" コマンドレットを実行してみました。

すると、その結果には "diskpart" の "list volume" や、「ディスクの管理画面」には表示される「EFI システムパーティション」というパーティション(100MB)が表示されていません。

 

 

このパーティションは、"mountvol" コマンドにより表示される「現在のマウントポイントとボリューム名の考えられる値」の一覧にも表示されません。

そのため、このパーティションが "Get-Volume" コマンドレットの結果に表示されないのは、「マウントされていない」ことが原因ではないかと考えられます。

"mountvol" コマンドのオプションを調べてみると "/S" というものがあり、その効能は「EFI システムパーティションを与えられたドライブにマウントします。」となっています。

おそらく、この方法でこのパーティションをマウントしてやれば、"Get-Volume" コマンドレットの結果にも表示されるようになるのでしょう。

が、ホストの環境はあまり弄りたくないので未確認です。

 

 

Start Screen Layout

Windows Server 2012 R2 (preview版) から新たに追加されたグループポリシー「Start Screen Layout」を設定してみました。

「コンピューターの構成」と「ユーザーの構成」の両方において、

「ポリシー」 => 「管理用テンプレート」  => 「タスクバーと[スタート]メニュー」  

の中に有ります。

※ローカルグループポリシーでも、ポリシーという階層が無いこと以外は同じです。

 

 

対象は Windows 8.1 以降なので、残念ながら Windows 8 に適用しても意味は無いのですが、 そう言われればやってみたくなるのが人情というもの。

ドメインコントローラーとして構築した Windows Server 2012 R2 の「ユーザーの構成」側でこのグループポリシーを有効化し、ドメインのメンバーである Windows 8 に適用させてみました。そして結果を確認するため、Windows 8 側で "rsop" コマンドにより「ポリシーの結果セット」を開きます。

すると、思った通り「管理用テンプレート」の中に「タスクバーと[スタート]メニュー」は無く、「レジストリの追加設定」がありました。これは、自身に存在しないグループポリシーが適用された場合の表示です。

そしてその中身も「Start Screen lauout」ではなく、一つのレジストリキー配下の二つのレジストリ値となっています。

[レジストリキー]

HKEY_CURRENT_USER¥Software¥Policies¥Microsoft¥Windows¥Explorer

[レジストリ値]

-  LockedStartLayout (REG_DWORD)  "1"

-  StartLayoutFile (REG_SZ)  "(空欄)" 

 

 

このレジストリキーは、デフォルトの状態では Windows 8.1 にも存在しないものです。このグループポリシーが適用されることで、始めて作成されます。

二つ目のレジストリ値が 空欄 なのは、グループポリシーの設定時に入力していなかったためです。このグループポリシーは有効にするだけでは意味が無く、レイアウトを定義する XML ファイルのパスを入力してやる必要があるのです。

 

 

XML ファイルを自分で書かないと駄目なのか・・・ と早とちりして挫折しかけましたが、グループポリシーの説明文(英語)をちゃんと読んでみると、PowerShell でエクスポート出来ることが分かりました。

Windows 8.1 ないし Windows Server 2012 R2 (ドメインコントローラーである必要はありません。)で雛形となるレイアウトを作っておき、そのコンピューターの powerShell で

"export-startlayout -path layout.xml -as xml"

というコマンドレットを実行します。これにより、現在のレイアウトを記述したXMLファイルがカレントディレクトリに作成されます。

 

 

このコマンドレットについては、早くも TechNet に解説が載っていました。

■ Start Screen Cmdlets in Windows PowerShell

http://technet.microsoft.com/library/dn283399(v=wps.630).aspx

※このページに載っているコマンドレットは、どれも PowerShell4.0 でしか使えないようです。

 

 

このXMLファイルをネットワーク上の共有フォルダに置いておき、そのパスをグループポリシーとして設定してやることで、適用先のドメインユーザーのスタート画面を統一出来るようです。

当然、ドメインユーザーはその共有フォルダへのアクセス権を持つ必要があると思われるのですが、今回の検証ではその手順は省いてちょっとズルしました。

グループポリシーが適用された Windows 8.1 のローカルディスク上に、Windows Server 2012 R2 にてエクスポートしたXMLファイルをコピーします。それからレジストリエディタを開き、上記「二つ目のレジストリ値」に直接そのファイルパスを書き込みます。

そうしてサインアウト&再サインインしたところ、スタート画面に元々あった「カレンダー」や「天気」等のタイルが ほとんど無くなり、Windows Server 2012 R2 のスタート画面が再現されました。

正確に言うと、「サーバーマネージャー」と「管理ツール」が欠けた状態です。サーバーマネージャーは元々サーバーOSにしか無いものなので当然なのですが、管理ツールまで無いのは不思議です。

 

 

XMLファイルをブラウザやメモ帳で開いてみると分かりますが、個々のアプリケーションは「AppID」という識別子により記述されています。

コンピューターにインストールされている各アプリケーションのAppIDは、powerShell の "get-startapps | fl" というコマンドレットで確認出来るのですが、Windows 8.1 で実行してみたところ、「管理ツール」のAppIDはありませんでした。(コントロールパネルの中には有るのに・・・)

 

 

なお、"get-startapps | fl" により確認出来た「電卓」の AppID を XML ファイルに書き足してやる事で、スタート画面に「電卓」を表示させる事が出来ました。

 

netsh の運命や如何に。

Windows Server 2012 R2 のプレビュー版が、6月中には公開されるそうですね。

ソースは、Microsoft Cloud というMicrosoft社によるツイッターアカウントです。

 

 

Windows Server 2012 では、コマンドプロンプトで " netsh " を実行し、次に " interface " 等を実行した時点で「Windows の将来のバージョンで、TCP/IP の Netsh機能が削除される可能性があります。」と表示されます。そしてPowerShell への移行を促されます。

さて、Windows Server 2012 R2 ではどうなっているでしょうか。

 

 

本当に無くなるのはさらに次のバージョンかもしれませんが、 "netsh" が無くなるとすると、これまで蓄積してきたスクリプト等の資産に少なからぬダメージがあるのではと思います。Windows Server 2008 R2 以降はパケットキャプチャまで出来ますからね。

Windows Server 2003 では OS 自体に標準で付属していたキャプチャツール「 Network Monitor 」が、Windows Server 2008 以降はダウンロードツールになりました。

商用稼働中のサーバーの場合、障害調査のためといえど、パケットキャプチャのためにフリーソフトをインストールすることなど言語道断!なケースもあります。たとえ Microsoft 純正であっても。そんな時には、 "netsh>trace" コマンドが有効な手段となりました。

パケットだけでなくネットワーク関連の様々な情報をまとめて採取出来るので、パケットキャプチャ自体が不要だったとしても、ネットワークの障害調査には有用なコマンドだと思います。

 

 

今のところ、Windows Server 2012 標準のPowershell v3.0 には、パケットキャプチャのコマンドレットは無いようです。Windows Server 2012 R2 には新しいバージョンが搭載されるのでしょうか。あるいは、バージョン自体は同じでモジュールの数が増えるのでしょうか。今から楽しみです。

 

 

ちなみに、Windows Server 2012 の PowerShell で "Get-Wild" と実行すると・・・

何も起こりません。

Windows Server 2012 R2  に期待します。

PowerShell における「変数」の扱い

PowerShell における「変数」について調べてみました。

PowerShell にはデフォルトでもいくつかの変数が存在します。PowerShell のバージョンを確認出来る "$psversiontable" は、やったことのある方も多いのではないでしょうか。(Windows Server 2008 R2 に標準搭載されている PowerShell V2.0 以降にしか存在しない変数ですが。)

 

 

コマンドプロンプトでも、「%」で前後を括る事により、「環境変数」を使うことが出来ました。例えば、現在ログオンしているユーザー名を表示するためには、"%username%" を実行します。(結果として表示されるユーザー名がコマンドとして実行されてしまうことを回避するには、"echo %username%" を実行します。)

ではPowerShell で"$username" を実行するとどうなるかというと、なにも起こりません。PowerShell においては、「$psversiontable」 のような「変数」と、従来の「環境変数」とは別物のようです。

調べた結果、PowerShell で「現在ログオンしているユーザー名」を表示するためには、"$env:username" を実行すればよいことが分かりました。

この「env:」とやらは一体なにものでしょうか? 実はこれ、「PSドライブ」の一種なのです。(調べれば調べるほど、次から次へと新しい用語が出てくるので参ります。。。)

 

 

PSドライブ」はPowerShell 独特の概念のようです。PowerShell だけが扱える仮想的なファイルシステムのようなもの、と言えるかもしれません。

従来のコマンドプロンプトでは、Cドライブ等のファイルシステムのディレクトリの中を見るには「dir」コマンドを用いましたが、同じような階層構造を持つレジストリの場合、別途「reg query」コマンドを用いる必要がありました。Powershell の場合はどちらも「PSドライブ」として扱えるので、「get-childitem」という共通のコマンドレットで両方の中を見る事が出来ます。

PSドライブの一覧を確認するには、"get-psdrive” を実行します。すると、マウントされているドライブやレジストリ(HKCU、HKLM)の他に、「Env」もあることが分かります。

 

 

「Env」がPSドライブであるならば、同じくPSドライブであるCドライブの中身を見る時と同じように、"get-childitem" コマンドレットでその中身を見る事が出来るはずです。そこで"get-childitem env:" を実行してみたところ、「環境変数」の一覧を取得することができました。もちろんその中には「USERNAME」もあります。

"$env:username" の意味は、「PSドライブ『Env』配下にある環境変数『USERNAME』に格納されている値を、『変数』として呼び出す」というものだったようです。

 

 

なお、「$」を先頭に付与する事で、PSドライブ配下の「変数ではないもの」を、変数のように扱う事が出来ます。

たとえば、"${C:¥1.txt}" を実行する事で、Cドライブ直下の「1.txt」というテキストファイルの中身を表示出来ます。(パス全体を{}で括る必要があるのは、おそらく「¥」が特殊記号だからでしょう。)

 

 

では、PSドライブを明示する必要が無い、「$psversiontable」のような「純粋な変数」の情報は、一体どこに存在するのでしょうか。

調べたところ、PSドライブ「Variable」の中にありました。このPSドライブの中にある「変数」は、変数名の先頭に「$」を付けるだけで(variable: は記述せずとも)呼び出す事が可能です。Variable 配下の変数の一覧は、"get-variable" ないし "get-childitem variable:" を実行することで確認出来ます。