PowerShell DSCはPowerShellが目指してきた姿の1つの目標であり、強力な技術基盤です。
PowerShellはDSCを提供にするにあたり、OMIの実装をWMIからクロスプラットフォームなCIMに移してきました。実際LinuxでPowerShell DSCを利用するPowerShell-DSC-for-Linuxも公開されています。
またWMF5.1からは、PowerShell Core、つまり .NET Core実装のクロスプラットフォームPowerShellも提供されます。シェルとして使い心地に課題は残るものの、着実にPowerShellでやると楽な部分、という意味での存在価値は間違いなく存在します。
さて、そんなPowerShellですがWMF5からは、DSCの実行ユーザーがBuiltIn/SYSTEM
だけでなく任意のユーザー指定が可能になりました。これによりユーザープロファイルに依存する処理も管理可能になりました。
ということで、先日資格情報マネージャーのリソースを公開したのでご紹介です。*1
資格情報マネージャーの操作
以前資格情報マネージャーをPowerShellで操作する方法を示しました。
現在はもう少しコードを修正していますが、ようはこれをPowerShell DSCでやれるようにします。
PowerShell Gallery からの導入
WMF5以降の環境ではワンライナーでDSCリソースを利用できます。
Install-Module -Name GraniResource -Scope AllUsers
幸いにもPowerShell Teamが公開している以外のリソースとして上位に位置するダウンロード数のようで嬉しい限りです。
実はリポジトリ名を DSCResources から GraniResource へこっそり変えていますが、ソースコードも変わらず公開しています。*2
利用
Grani_CredentialManagerを利用することで、資格情報マネージャーを管理できます。デフォルトは、DSCの実行ユーザー、つまりSYSTEMアカウントが対象です。このアカウントができるということは、SYSTEMで動作するサービスの資格情報を管理できます。
https://gist.github.com/guitarrapc/93713a4a2577abd6b9312677b641d307
また、PsRunAsCredential
を指定することで任意のアカウントの資格情報も操作できます。
https://gist.github.com/guitarrapc/453d573ca2bf67e8d2a13c24cc9cf166
Grani_CredentialManager について
前回のリソース作成から、PowerShellチームへの今トリビュートを重ねる中で得た知見を使って少し書き方を変えました。
本体とヘルパーの分離
いわゆる*-TargetResource
がPowerShell DSCを関数で記述したときの本体です。*3
今回のリソースは、ロジックをC# に閉じ込めていたこともあり本体の処理は非常にシンプルです。が、それでもヘルパーとなる補助関数は *helper.psm1として分離しました。
ポイントは .psm1、つまりモジュールとしての分離です。本体のGrani_CredentialManager.psm1で公開していないので内部モジュール扱いなのですが、Pesterテストを含めた取り扱いの良さから .ps1ではなく .psm1にしています。この手法は、xTimeZoneリソースでも行っておりPowerShellにおけるプライベートな関数を閉じ込めるにあたりとてもいい手法だと思います。
呼び出しもシンプルですしね。
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath "Grani_CredentialManagerHelper.psm1") -Verbose:$false -Force
PowerShell のまずいポイント対策
PowerShellには致命的なポイントがあります。それが、バイナリモジュールを利用する場合DLLをロックしてしまうということです。つまり、C# で書いてビルドしたDLLをDSCなどで利用すると、DSC実行中はファイルロックされます。これはデプロイの観点からみると非常に問題で、Windows嫌いになります。その対策として、以前Assembly.Loadを利用したダイナミックモジュールによるロック回避も提示しました。
しかしPowerShell DSCに限らず、この手法は正直いやです。人道的とか言いながら逆説的な複雑性を増しています。
そこで今回をはじめとして、PowerShellのAdd-Type
Cmdletを利用したC# コードのオンザフライコンパイルによるコード注入を好んで使っています。これならばファイルロックを回避できます。問題になりえるのは、同一AppDomainへの同一名前空間、クラス、シグネチャの多重インポートですがPowerShell DSCは毎回の実行で別AppDomainを起動するので問題ありません。
具体的にはこのC#コードを、ヘルパーとして差し込んでいます。
$code = Get-Content -Path (Join-Path -Path $PSScriptRoot -ChildPath "CredentialManager.cs") -Raw $referenceAssemblies = @("System", "System.Linq", "System.ComponentModel", "System.Management.Automation", "System.Runtime.InteropServices") Add-Type -TypeDefinition $code -ReferencedAssemblies $referenceAssemblies -ErrorAction SilentlyContinue;
あとは、PowerShellから扱いやすいように薄くラッパを作って、DSC本体から呼び出すだけです。
PowerShell Team 公式リソースのテスト
PoweShell Teamは、DSCリソースに関して、DscResources/CONTRIBUTING.mdにコントリビューションガイドラインを公開しています。
Grani_CredentialManagerは、ガイドラインに沿うようにテストを組んであります。今後DSCリソースを書いて公開する場合のサンプルにでもなれば幸いです。
https://github.com/guitarrapc/GraniResource/tree/master/Test/Grani_CredentialManager
特にこの辺りは基礎となります。
$global:dscModuleName = 'GraniResource' # Import $modulePath = (Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)).Replace("Test","DSCResources") $global:dscResourceName = (Split-Path -Path $modulePath -leaf) $moduleFileName = $global:dscResourceName + ".psm1" Import-Module -Name (Join-Path -Path $modulePath -ChildPath $moduleFileName) -Force # Prerequisite for Initialize-TestEnvironment in Domain Environment Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope Process # Initialize [String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path)) if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) { & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\')) } else { & git @('-C',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\'),'pull') } Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force $TestEnvironment = Initialize-TestEnvironment -DSCModuleName $global:dscModuleName -DSCResourceName $global:dscResourceName -TestType Unit # Begin Test try { InModuleScope $global:dscResourceName { # テストロジック } } finaly { Restore-TestEnvironment -TestEnvironment $TestEnvironment }
コントリビューションガイドの通り、PesterとDscResource.Testsを使うのがいいでしょう。もう少しパイプラインベースでテストを組めると書きやすいんですが、まだまだフレームワークもなく事実上Pesterしか確立したテスト手法がないですね。
DscResource.TestsはAppVeyorでのテストを通すことを前提で組んでるのか、ローカルでは妙な挙動をするのでバグ報告もしておきました。とりあえず現状でもふつーに使えるはずです。*4
まとめ
これで資格情報マネージャーもPowerShell DSCでハンドルできました。そもそも資格情報が必要になる処理はRunDeckのように外から管理したところですが、なかなか難しいです。
悩ましいものの、いったんは目指した課題解決はできたので試していただけると幸いです。