tech.guitarrapc.cóm

Technical updates

Powershell のZip処理をDSCと比較してみる

PowerShell V4.0 では DSC があることに言及してきました。

さて、このDSCですが標準で各種リソースに対して処理が可能です。その中には zip解凍処理処理も含まれています。

Built-In Windows PowerShell Desired State Configuration Resources

Provider Description
Archive Resource Unpacks archive (.zip) files at specific paths on target nodes.
Environment Resource Manages system environment variables on target nodes.
File Resource Manages files and directories on target nodes.
Group Resource Manages local groups on target nodes.
Log Resource Logs configuration messages.
Package Resource Installs and manages packages, such as Windows Installer and setup.exe packages, on target nodes.
Process Resource Configures Windows processes on target nodes.
Registry Resource Manages registry keys and values on target nodes.
Role Resource Adds or removes Windows features and roles on target nodes.
Script Resource Runs Windows PowerShell script blocks on target nodes.
Service Resource Manages services on target nodes.
User Resource Manages local user accounts on target nodes.

これ以外のカスタムリソースの構築に関しては後日記事にします。

Build Custom Windows PowerShell Desired State Configuration Resources

今回は、zip処理に関して、DSCを使わない場合と使った場合での違いを見てみましょう。

目次

DSCを使わない zip処理

いくつかあります。 特に、comを使った処理と、ZipFile クラス を使った処理 が有名かと思います。*1*2

なお、comを使った処理は、もはやバッドノウハウなので使う理由がありませんしここではあげません。

ZipFileクラスを利用する

msdn - ZipFile クラス

.NET 4.5から利用できます。が、ご存じのとおり、 Windows PowerShell は V3.0/4.0 共に .NET4.0ベースなのでこのままでは使えません。

PS D:\> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      4.0
WSManStackVersion              3.0
SerializationVersion           1.1.0.1
CLRVersion                     4.0.30319.33440
BuildVersion                   6.3.9600.16384
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0}
PSRemotingProtocolVersion      2.2

そこで、みんな大好き Add-Type を利用します。

Add-Type -AssemblyName "System.IO.Compression.FileSystem"

あとは、C#コードと同様の処理をPowerShellで記述するだけです。

Zip圧縮

利用しやすいように function にしました。

function New-ZipCompression{

    [CmdletBinding()]
    param(
        [parameter(
            mandatory,
            position = 0,
            valuefrompipeline,
            valuefrompipelinebypropertyname)]
        [string]
        $source,

        [parameter(
            mandatory,
            position = 1,
            valuefrompipeline,
            valuefrompipelinebypropertyname)]
        [string]
        $destination,

        [parameter(
            mandatory = 0,
            position = 2)]
        [switch]
        $quiet
    )

    $zipExtension = ".zip"

    try
    {
        Add-Type -AssemblyName "System.IO.Compression.FileSystem"
    }
    catch
    {
    }

    if (-not($destination.EndsWith($zipExtension)))
    {
        throw ("destination parameter value [{0}] not end with extension {1}" -f $destination, $zipExtension)
    }
        
    try
    {
        $destzip = [System.IO.Compression.Zipfile]::Open($destination,"Update")
        $compressionLevel = [System.IO.Compression.CompressionLevel]::Optimal
        $files = Get-ChildItem -Path $source -Recurse | where {-not($_.PSISContiner)}

        foreach ($file in $files)
        {
            $file2 = $file.name

            if ($quiet)
            {
                [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($destzip,$file.fullname,$file2,$compressionLevel) > $null
                $?
            }
            else
            {
                [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($destzip,$file.fullname,$file2,$compressionLevel)
            }
        }

        $destzip.Dispose()
    }
    catch
    {
        Write-Error $_
    }
}

圧縮する時はこのようにします。

d:\testを hoge.zipにzipする場合は次のようにします。

New-ZipCompression -source D:\test -destination d:\hoge.zip

また、-quietスイッチで出力結果がホスト表示されません。

New-ZipCompression -source D:\test -destination d:\hoge.zip -quiet

Zip解凍

こちらも利用しやすいように function にしました。

function New-ZipExtract{

    [CmdletBinding()]
    param(
        [parameter(
            mandatory,
            position = 0,
            valuefrompipeline,
            valuefrompipelinebypropertyname)]
        [string]
        $source,

        [parameter(
            mandatory,
            position = 1,
            valuefrompipeline,
            valuefrompipelinebypropertyname)]
        [string]
        $destination,

        [parameter(
            mandatory = 0,
            position = 2)]
        [switch]
        $quiet
    )

    $zipExtension = ".zip"

    try
    {
        Add-Type -AssemblyName "System.IO.Compression.FileSystem"
    }
    catch
    {
    }

    if (-not($source.EndsWith($zipExtension)))
    {
        throw ("source parameter value [{0}] not end with extension {1}" -f $source, $zipExtension)
    }
        
    try
    {
        $sourcezip = [System.IO.Compression.Zipfile]::Open($source,"Update")
        $compressionLevel = [System.IO.Compression.CompressionLevel]::Optimal

        if ($quiet)
        {
            [System.IO.Compression.ZipFileExtensions]::ExtractToDirectory($sourcezip,$destination) > $null
            $?
        }
        else
        {
            [System.IO.Compression.ZipFileExtensions]::ExtractToDirectory($sourcezip,$destination)
        }

        $sourcezip.Dispose()
    }
    catch
    {
        Write-Error $_
    }
}

解凍する時はこのようにします。

d:\hoge.zipを d:\hogehogeにzip解凍する場合は次のようにします。

New-ZipExtract -source d:\hoge.zip -destination d:\hogehoge

また、-quietスイッチで出力結果がホスト表示されません。

New-ZipExtract -source d:\hoge.zip -destination d:\hogehoge -quiet

GitHub

コードを置いておきます。

PowerShellUtil / PS-Zip / PS-Zip.psm1

リモートマシンでのzip処理

DSCを使わないZip処理の場合、valentiaを利用するならリモートマシンでの処理も簡単ですが、valentiaのない環境では.....面倒ですね。

しかし、PowerShell V4.0ならDSCを利用することでZip解凍が簡単になります。

DSCを使った zip解凍処理

DSCの基本は、 configurationキーワードで、ノードとリソースを指定して状態などを宣言することです。

あとは、configurationで実行する内容をmofファイルに吐き出して、Start-DscConfigurationコマンドレットで実行するだけです。

これは Zip解凍でも変わりません。対象ノードに対して、リソースが Archiveで、対象pathが存在する(Present)を前提にDestinationへの解凍を宣言します。

configuration宣言

実際に処理を見てみましょう。

先ほど文章で示した内容をconfigurationを使って宣言するだけです。

configuration UnZipFile {

    param (
        [Parameter(mandatory)]
        [string[]]$ComputerName,
        [string]$Path,
        [string]$Destination
    )
    
    node $ComputerName {
        archive ZipFile {
        Path = $Path
        Destination = $Destination
        Ensure = 'Present'
        }
    }
}

configurationを実行してmofを生成する

対象pathとdestinationに対応した .mofを出力します。 mofファイルは、-OutputPathを指定しないと、現在のパスにconfigurationで宣言した名称(この場合は、UnZipFile)としてフォルダが生成されます。

では、 localostに対して、d:\hoge.zipがあった場合に d:\hogehogeへ解凍する操作とします。

UnZipFile -ComputerName localhost -Path D:\hoge.zip -Destination d:\hogehoge -Verbose
    Directory: D:\hoge\UnZipFile


Mode                LastWriteTime     Length Name                                                                                                                                                                                     
----                -------------     ------ ----                                                                                                                                                                                     
-a---        2013/10/07      8:24       1140 localhost.mof  

mofの実行

mofは出力したフォルダを指定して実行します。 mofファイル自体を実行するわけではないことに注意してください。

こうすることで、対象フォルダに生成されたノード分の処理がまとめて実行されるわけです。

Start-DscConfiguration -Path .\UnZipFile -Wait -Force -Verbose

コマンドに加えた -Wait スイッチと -Verboseスイッチで処理内容が見えます。特に-Verboseスイッチは、定型処理でない限りぜひ付けて実行することを推奨します。

hogehoge.zipファイルがない場合は、処理が失敗します。

VERBOSE: Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = SendConfigurationApply,'className' = MSFT_DSCLocalConfigurationManager,'namespaceName' = root/Microsoft/Windows/DesiredStateConfiguration'.
VERBOSE: An LCM method call arrived from computer WINDOWS81X64 with user sid S-1-5-21-3867976201-3469415403-658829222-1001.
VERBOSE: [WINDOWS81X64]: LCM:  [ Start  Set      ]
VERBOSE: [WINDOWS81X64]: LCM:  [ Start  Resource ]  [[Archive]ZipFile]
VERBOSE: [WINDOWS81X64]: LCM:  [ Start  Test     ]  [[Archive]ZipFile]
VERBOSE: [WINDOWS81X64]: LCM:  [ End    Test     ]  [[Archive]ZipFile]  in 0.1860 seconds.
PowerShell provider MSFT_ArchiveResource  failed to execute Test-TargetResource functionality with error message: The specified source file D:\hoge.zip does not exist or is not a file
Parameter name: Path 
    + CategoryInfo          : InvalidOperation: (:) [], CimException
    + FullyQualifiedErrorId : ProviderOperationExecutionFailure
    + PSComputerName        : localhost
 
VERBOSE: [WINDOWS81X64]: LCM:  [ End    Set      ]
LCM failed to move one or more resources to their desired state.
    + CategoryInfo          : NotSpecified: (root/Microsoft/...gurationManager:String) [], CimException
    + FullyQualifiedErrorId : MI RESULT 1
    + PSComputerName        : localhost
 
VERBOSE: Operation 'Invoke CimMethod' complete.
VERBOSE: Time taken for configuration job to complete is 0.667 seconds

hogehoge.zipファイルがあれば、処理が実行されます。

VERBOSE: Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = SendConfigurationApply,'className' = MSFT_DSCLocalConfigurationManager,'namespaceName' = root/Microsoft/Windows/DesiredStateConfiguration'.
VERBOSE: An LCM method call arrived from computer WINDOWS81X64 with user sid S-1-5-21-3867976201-3469415403-658829222-1001.
VERBOSE: [WINDOWS81X64]: LCM:  [ Start  Set      ]
VERBOSE: [WINDOWS81X64]: LCM:  [ Start  Resource ]  [[Archive]ZipFile]
VERBOSE: [WINDOWS81X64]: LCM:  [ Start  Test     ]  [[Archive]ZipFile]
VERBOSE: [WINDOWS81X64]:                            [[Archive]ZipFile] The destination file d:\hogehoge\hogehoge.wav was missing or was not a file
VERBOSE: [WINDOWS81X64]: LCM:  [ End    Test     ]  [[Archive]ZipFile]  in 0.2860 seconds.
VERBOSE: [WINDOWS81X64]: LCM:  [ Start  Set      ]  [[Archive]ZipFile]
VERBOSE: [WINDOWS81X64]:                            [[Archive]ZipFile] The configuration of MSFT_ArchiveResource is starting
VERBOSE: [WINDOWS81X64]:                            [[Archive]ZipFile] The archive at D:\hoge.zip was unpacked to destination d:\hogehoge
VERBOSE: [WINDOWS81X64]:                            [[Archive]ZipFile] The configuration of MSFT_ArchiveResource has completed
VERBOSE: [WINDOWS81X64]: LCM:  [ End    Set      ]  [[Archive]ZipFile]  in 0.6880 seconds.
VERBOSE: [WINDOWS81X64]: LCM:  [ End    Resource ]  [[Archive]ZipFile]
VERBOSE: [WINDOWS81X64]: LCM:  [ End    Set      ]
VERBOSE: [WINDOWS81X64]: LCM:  [ End    Set      ]    in  1.0440 seconds.
VERBOSE: Operation 'Invoke CimMethod' complete.
VERBOSE: Time taken for configuration job to complete is 1.085 seconds

どうでしょうか。このように DSCを使うことで、Zip解凍のための処理をfunctionで書くことなく宣言するだけで実行できています。

DSCに望むのは、まさにこのあたりの リモートマシン(ノード)に対する処理が宣言で済み、繰り返し実行しても結果が変わらないことです。

Windows 8.1と Windows Server 2012のリリースが間近です。DSC楽しみですね。

*1:ZipPackage クラスを使った処理も一時期話に上りましたが使いにくいので

*2:7zipを使った処理は対象外です。