2012年11月28日水曜日

◆ファイルを暗号化する

以下のサイトを参考に暗号化の処理を書いてみた。
10 行でズバリ !! 暗号化 (C#) 言語: C#

現在は「AES」が主流のようだが、このサンプルは「Triple DES」を使っている。

ここで使用する「TripleDESCryptoServiceProvider」クラスをインスタンシングすると秘密鍵と初期化ベクターが作られる。
本来はそれを送信相手の公開鍵で暗号化して送ることになるのだろうが、今回はサンプルなので判りやすさを優先し、両方ともコンスタントで指定している。

あとは単純にC#をPowershellに落としただけなので結果だけ載せておく。
デスクトップに元ファイルを作っておき、エンコードしたファイル、デコードしたファイルをそれぞれ作成している。

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028

$inPath = "F:\Desktop\myTest.txt"
$encPath = "F:\Desktop\Enc.txt"
$decPath = "F:\Desktop\Dec.txt"

$desKey = [byte[]](1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1)
$desIV = [byte[]](0,0,0,0,0,0,0,0)
$src = [System.IO.File]::ReadAllBytes($inPath)

#暗号化
$desProvider =
 
  
New-Object System.Security.Cryptography.TripleDESCryptoServiceProvider
$encryptor = $desProvider.CreateEncryptor($desKey,$desIV)
$ms = New-Object System.IO.MemoryStream
$cs = New-Object System.Security.Cryptography.CryptoStream
(
  
$ms, $encryptor, [System.Security.Cryptography.CryptoStreamMode]::Write)
$cs.Write($src,0,$src.Length)
$cs.Close()
[System.IO.File]::WriteAllBytes($encPath,$ms.ToArray())

#複合化
$src = [System.IO.File]::ReadAllBytes($encPath)
$decryptor = $desProvider.CreateDecryptor($desKey,$desIV)
$ms = New-Object System.IO.MemoryStream
$cs = New-Object System.Security.Cryptography.CryptoStream
(
  
$ms, $decryptor, [System.Security.Cryptography.CryptoStreamMode]::Write)
$cs.Write($src,0,$src.Length)
$cs.close()
[System.IO.File]::WriteAllBytes($decPath,$ms.ToArray())
<!--EndFragment

参考までに、キー及び初期化ベクターをファイルに作成するスクリプトは以下のようになる。

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017

#◆Key作成.ps1
$currntPath = Split-Path $myInvocation.MyCommand.path
$keyPath = Join-Path $currntPath "Key.dat"
$ivPath = Join-Path $currntPath "IV.dat"
$now = (Get-Date).ToString("yyyyMMddHHmm")
if(Test-Path $keyPath
){
 
ren $keyPath "$keyPath.$now"
}
if(Test-Path $ivPath
){
 
ren $ivPath "$ivPath.$now"
}

#暗号化キーおよび初期化ベクター作成
$desProvider =
 
  
New-Object System.Security.Cryptography.TripleDESCryptoServiceProvider
[System.IO.File]::WriteAllBytes($keyPath,$desProvider.Key)
[System.IO.File]::WriteAllBytes($ivPath,$desProvider.IV)
</DI

◆バイナリファイルの読み書き

Get-ContentコマンドレットのEncodingパラメータに「Byte」を指定するとバイナリーでの読み込みができることに最近気づいた。

Set-Contentに同様の指定をすると書き込みもOKだ。

001
002

[Byte[]]$bFile = Get-Content "F:\Desktop\NorthwindJ.mdf" -Encoding Byte
Set-Content "F:\Desktop\NorthwindJ2.mdf" -Value $bFile -Encoding Byte

これだけならばめでたしめでたしなのだが、安心してちょっと大きめのファイル(といっても数M程度)を指定すると大変なことになってしまう。

Get-Content : 種類 'System.OutOfMemoryException' の例外がスローされました。

発生場所 C:\Users\kumagai_mitsugu\AppData\Local\Temp\efd7ec81-99b3-4bc1-804a-b983b38f7e31.ps1:1 文字:29
+ [Byte[]]$bFile = Get-Content <<<<  "F:\Desktop\NorthwindJ.mdf" -Encoding Byte
    + CategoryInfo          : NotSpecified: (:) [Get-Content]、OutOfMemoryException
    + FullyQualifiedErrorId : System.OutOfMemoryException,Microsoft.PowerShell.Commands.GetContent
   Command

数M程度のファイルもまともに扱えない機能って?と思ったりもする。

そもそも「Byte」での読み込みは非常に遅い。
どうやら本当に「Byte」単位で読んでいるらしい。

そこで、読み込みのバッファとして「ReadCount」パラメータに「10KB」とかを適当に指定してあげる。

>Get-Content "F:\Desktop\NorthwindJ.mdf" -Encoding Byte -ReadCount 10KB

すると読み込みは一瞬で終わるようにはなるのだが、読み込まれた形式が「10KB」の配列になってしまう。
例えば、ファイルが100KBだとすると10個の10KBのobject配列が出来上がってしまう。

結局「Set-Content」するためにはそれらをByte配列にする必要があるので、そこに時間がかかり元の木阿弥となる。

もう少し何か方法が有るのかもしれないが、.Netframeworkクラスを使えば単純に出来る処理が、コマンドレットを使うと面倒になるようでは本末転倒である。

という事で、これまで通り以下の方法で行くこととした。

001
002

$src = [System.IO.File]::ReadAllBytes("F:\Desktop\NorthwindJ.mdf")
[System.IO.File]::WriteAllBytes("F:\Desktop\NorthwindJ2.mdf",$src)

◆イベントログを抽出して保存する

例えば、先月分のイベントログを抽出して保存したいとする。
ファイル形式はもとのままで(.evtx)

イベントログの抽出はこれまでも何度かやっていて、いくつか方法はあると思うのだが、保存となるとどうやるのが一般的なのだろう。

抽出無しにすべてを保存するのであればWMIで取ってきて「BackupEventlog」メソッドあたりが使えそうだ。

Powershellのコマンドレットではちょっと見当たらないなぁ・・・。

色々と調べてみたところ「EventLogSession」クラスの「ExportLog」メソッドというのが使えそうである。

.Netframework3.5で追加されたっぽいので、Powershell2.0で使えないのかと思ったが試してみたところ問題なく動くようだ。(理由は良く判っていないのだが、単に3.5がインストールされていれば良いのだろうか・・・)

ログを抽出する条件については以下で使ったXPath形式で良さそうだ。
PowerShell: ◆イベントログを抽出する FilterXPath

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022

#◆イベントログ抽出
$fromDay = -22    #前日:-1
$days = 3         #開始からの日数
$logname = "System"
$outfile = "F:\Desktop\mySystemLog.evtx"

$startTime =
 
 
[DateTime](Get-Date).AddDays($fromDay).ToString("yyyy/MM/dd 00:00:00")
$startUtcTime =
 
 
[System.TimeZoneInfo]::
ConvertTimeToUtc(
   
$startTime).ToString("yyyy-MM-ddTHH:mm:ssZ")
$endUtcTime =
 
 
[System.TimeZoneInfo]::
ConvertTimeToUtc(
   
$startTime.AddDays($days)).ToString("yyyy-MM-ddTHH:mm:ssZ")
$filter = 
@"
  Event/System/TimeCreated[@SystemTime>='$startUtcTime'] and
  Event/System/TimeCreated[@SystemTime<'$endUtcTime']
"@
 

$evsession =
 
 
New-Object -TypeName System.Diagnostics.Eventing.Reader.EventLogSession
$evsession.ExportLog($logname,"LogName",$filter,$outfile)
<

◆変数名に変数を使う

Set-Variableコマンドレットを使って変数を定義すれば名前を変数で指定することができる。

001
002
003
004

1..5 | %{
 
Set-Variable -Name var$_ -Value $_
}
Get-Variable var*

Name                           Value                      
----                           -----                      
var1                           1                          
var2                           2                          
var3                           3                          
var4                           4                          
var5                           5                          

◆賢くなったSelect-Object(Ver3)

以下のような処理、これまでは終わるまでに100秒掛かっていたのだが、Ver3では3秒で終わる。

001
002
003
004
005
006

function heavyFunc
{
 
1..100 | %{$_;start-sleep -Seconds 1
}
}


heavyFunc | select -First 3

◆関数の戻り値への注意(メモ)

最近仕事の関係で少しC#を使っている。
そのせいでC#頭になっていたためか、Powershellで以下のようなエラーに悩まされた。(実際にはもう少し大きな関数)

001
002
003
004
005
006
007

function MonthNext([Datetime]$p1){
  $aList.Add($p1
)
  return $p1.AddMonths(1
)
}

$aList = New-Object System.Collections.ArrayList
$翌月 = MonthNext (Get-Date)
$翌々月 = MonthNext $翌月

MonthNext : パラメーター 'p1' の引数変換を処理できません。"System.Object[]" の値を "
System.Object[]" 型から "System.DateTime" 型に変換できません。

発生場所 C:\Users\kumagai_mitsugu\AppData\Local\Temp\f481cf43-ee2
c-41bc-ae84-155af6663cc7.ps1:7 文字:17
+ $翌々月 = MonthNext <<<<  $翌月
    + CategoryInfo          : InvalidData: (:) [MonthNext]、P
   arameterBindin...mationException
    + FullyQualifiedErrorId : ParameterArgumentTransformatio
   nError,MonthNext

7行目の引数「$翌月」が日付型に変換できないというエラーだ。

ん~、AddMonthsしてreturnしているんだから当然日付型のはずっと悩んでしまった。

調べてみると戻り値がこんな感じになっている。

PS>$翌月
0

2012年12月28日 9:33:52


PS>$翌月.GetType()

IsPublic IsSerial Name                                     BaseType                  
-------- -------- ----                                     --------                  
True     True     Object[]                                 System.Array              

どうやら「0」と「日付」のオブジェクト配列となっているようである。

「0」って何だ?としばらく悩んだ挙句、ArrayListにAddしたときの戻り値だと気付いた。

判ってしまえば何の事はない。
ArrayListのAddに戻り値があるのは何度も経験しているし、Powershellの場合「return」に特に意味はないことも判っている(あっても無くても結果は同じ)、関数内の複数個所で出力すれば纏めてリターンされることも判っている。

すべてが判っていることなのだが、少しずつの錯覚が重なって結構嵌ってしまった。

こんな時の為に戻り値を一旦クリアできたら便利な気もするのだが・・・。

2012年11月13日火曜日

◆OutputType属性の指定でインテリセンスを効かせる

自前の関数を使う場合、以下のようなケースでは通常インテリセンスが効かない。
image

バージョン3からは「OutputType」属性を指定することでインテリセンスを有効にすることができるのだとか。
image

ただし、試してみた感じでは、これが有効になるのは変数が「null」の場合。
変数に値が入っている場合はそちらが優先される。

なので、後から属性を付加した場合などは意図したインテリセンスにならない場合もある。

まぁ、スクリプト言語なので妥当な仕様であろう。

 

これまではインテリセンスを効かせるために、ある程度実行しながら書き進めることが多かった。
わざわざ属性を指定する手間を考えるとどちらが良いかは個人の好みだろうか・・・。