目次
デジタル金庫環境の整備とPowerShell
デジタル金庫環境の整備
筆者はWindows Pro版というWindowsのちょっとだけいいエディションを使っているので、これを最大限活用するべく、BitLockerによって暗号化された複数の暗号化VHDと、クロスプラットフォームスクリプト環境のPowerShell、そして運用にタスクスケジューラを利用したデジタル金庫システムを前から構築している。本稿ではこのシステムの構築においてビジネスロジックを書くのに利用した、PowerShellについて思ったことと、その実装について軽く記述したい。
PowerShellはすごい!
PowerShellは結構すごい。
NETのインターフェースを叩ける
まず、PowerShellから.NETのAPIを使える。これはPowerShellでもスタックやキューなど、C#と共通の基本なデータ構造やその他クラスを使うことができることを意味する。例えば、可変長配列を使いたいとき、System.Collections.Generic.Listとすると、.NETライブラリのSystem.Collections.Generic名前空間上にあるListクラスを実際に利用できる。筆者はこれまでC#を触ったことがなかったが、.NETのAPIはC#のほかにもVisual Basicなどからも利用できるという。こういった共通のライブラリを色々な言語から叩けるという仕組みがあるのは感心する。
$List = New-Object System.Collections.Generic.List[System.Object]
1..5 | ForEach-Object { $List.Add($_) }
Write-Host -NoNewLine $List # 1 2 3 4 5
全部オブジェクト
また、UNIXシェルスクリプティングにおいて扱う主役のデータ形式が基本すべてプレーンテキストであるというところに美しさがあるならば、PowerShellのいいところはそれが全部オブジェクトであるところにあると思う。あるオブジェクトの出力に対して、 Get-Member
などのコマンドをパイプでつないであげれば、なかにどんなプロパティがあるのかが一目瞭然である。この出力がどんなデータを引っ張り出せるのか、ということが簡単にわかる。
たとえば、ボリューム情報を出力するコマンドレットであるGet-Volume
の出力に対して、シンプルにオブジェクトのプロパティをリストアップしてくれるFormat-List
を適用することができる。
Get-ChildItem C:\Windows\Media\onestop.mid | Format-List
結果
Directory: C:\Windows\Media
Name : onestop.mid
Length : 40075
CreationTime : 2022/05/07 14:18:59
LastWriteTime : 2022/05/07 14:18:59
LastAccessTime : 2024/04/24 14:03:39
Mode : la---
LinkType : HardLink
Target :
VersionInfo : File: C:\Windows\Media\onestop.mid
InternalName:
OriginalFilename:
FileVersion:
FileDescription:
Product:
ProductVersion:
Debug: False
Patched: False
PreRelease: False
PrivateBuild: False
SpecialBuild: False
Language:
そして、こうしたプロパティへのアクセスは、ドット記法を使って行える。
(Get-Volume C).UniqueId
# \\?\Volume{c94f1507-47ee-4df0-85db-eaa2205672da}\
また、メンバーを表示するGet-Member
コマンドレットを適用することができる。-MemberType
オプションを指定することによってさらに絞り込むことも可能である。
Get-Volume C | Get-Member
結果
TypeName: Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_Volume
Name MemberType Definition
---- ---------- ----------
Dispose Method void Dispose(), void IDisposable.Dispose()
Equals Method bool Equals(System.Object obj)
GetCimSessionComputerName Method string GetCimSessionComputerName()
GetCimSessionInstanceId Method guid GetCimSessionInstanceId()
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
AllocationUnitSize Property uint AllocationUnitSize {get;}
DriveLetter Property char DriveLetter {get;}
FileSystem Property string FileSystem {get;}
FileSystemLabel Property string FileSystemLabel {get;set;}
ObjectId Property string ObjectId {get;}
PassThroughClass Property string PassThroughClass {get;}
PassThroughIds Property string PassThroughIds {get;}
PassThroughNamespace Property string PassThroughNamespace {get;}
PassThroughServer Property string PassThroughServer {get;}
Path Property string Path {get;}
PSComputerName Property string PSComputerName {get;}
Size Property ulong Size {get;}
SizeRemaining Property ulong SizeRemaining {get;}
UniqueId Property string UniqueId {get;}
DedupMode ScriptProperty System.Object DedupMode {get=switch ($this.psBase.CimInstanceProperties["Dedu…
DriveType ScriptProperty System.Object DriveType {get=switch ($this.psBase.CimInstanceProperties["Driv…
FileSystemType ScriptProperty System.Object FileSystemType {get=switch ($this.psBase.CimInstanceProperties[…
HealthStatus ScriptProperty System.Object HealthStatus {get=switch ($this.psBase.CimInstanceProperties["H…
OperationalStatus ScriptProperty System.Object OperationalStatus {get=$_status = @();…
このように、シェルスクリプトでは作成がちょっと大変な中規模のスクリプトを、PowerShellではオブジェクト指向プログラミング言語のパラダイムを用いて作れるのが魅力的だと思う。ただし、暗黙の型変換が普通に起こったり、スクリプトの不具合を追跡しづらかったりするのが非常につらいところではある。あとは個人的な問題ではあるが、単純にWindowsの知識がなくて基礎知識を得るのに終始してしまい、かなり時間を浪費してしまう点も否めない。
実装
実装とCSVファイル
# Utilization of virtual disk encrypted with bitlocker as digital vault, with a support of automated mount with powershell on startup.
param (
[Parameter(Mandatory)][string] $File,
[switch] $Detach = $false
)
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw "This PowerShell script needs to be executed by an account with priviledges."
}
class Storage {
[string]$Path
[char]$DriveLetterExpected
[string]$UniqueIdExpected
Storage(
[string]$Path,
[char]$DriveLetterExpected,
[string]$UniqueIdExpected
) {
$this.Path = $Path
$this.DriveLetterExpected = $DriveLetterExpected
$this.UniqueIdExpected = $UniqueIdExpected
}
[bool] ImageAttached() {
return (Get-DiskImage $this.Path).Attached
}
[bool] LetterAssigned() {
return $this.DriveLetterExpected -in (Get-Volume).DriveLetter
}
[void] Mount() {
if (!($this.ImageAttached())) {
Mount-DiskImage -ImagePath $this.Path -NoDriveLetter
}
}
[void] AssignDriveLetter() {
$DriveLetter = [string]$this.DriveLetterExpected + ":"
mountvol.exe $DriveLetter $this.UniqueIdExpected
if (!($this.LetterAssigned())) {
throw "Failed to assign the drive letter $DriveLetter"
return
}
Write-Output $this
}
[void] Dismount() {
Dismount-DiskImage -ImagePath $this.Path
}
[void] Unlock($SecureString) {
$drive = Get-Volume $this.DriveLetterExpected
if ($drive.UniqueId -ne $this.UniqueIdExpected) {
throw "The unique id of mounted volume must be identical to {0}:, but is {1}:." -f $drive.UniqueId, $this.UniqueIdExpected
}
Unlock-BitLocker -MountPoint $this.DriveLetterExpected -Password $SecureString
}
}
$Vhd = New-Object System.Collections.Generic.List[System.Object]
Import-Csv $File -Header 'Path', 'DriveLetter', 'UniqueId' | ForEach-Object {
$Vhd.Add([Storage]::new($_.Path, $_.DriveLetter, $_.UniqueId))
}
switch ($Detach) {
$true {
$Vhd | ForEach-Object { $_.Dismount() }
}
Default {
$Password = Read-Host "Enter Password" -AsSecureString
$Vhd | ForEach-Object { $_.Mount() }
$Vhd | ForEach-Object { $_.AssignDriveLetter() }
$Vhd | ForEach-Object { $_.Unlock($Password) }
}
}
"C:/path/to/Harddisk0.vhdx","H","\\?\Volume{00000000-0000-0000-0000-000000000000}\"
"C:/path/to/Harddisk1.vhdx","G","\\?\Volume{00000000-0000-0000-0000-000000000000}\"
"C:/path/to/Harddisk2.vhdx","I","\\?\Volume{00000000-0000-0000-0000-000000000000}\"
基本的には以下のようにして利用する。
.\Mount-VHDs.ps1 <PATH> [-Detach]
スクリプトに対して手動で-Detach
みたいな引数を付与して起動すると、一括でドライブをアンマウントすることもできる機能も付けている。画面を共有するときなどに叩くとフールプルーフとして役立つ。
なお、このプログラムはディスクの暗号化パスワードがすべて同じという前提の上に成り立っているものなので、必ずしもすべての人の用途に合わない場合もあることに注意すること。
おまけ
一応、動作を知りたい人のために、PowerShellスクリプト部分の実行動作を説明する。まずこのスクリプトが動く前提は以下のようになる。
- Windows Pro Edition
- BitLockerによって暗号化され、ユーザーによる暗号化の解除が可能な仮想ディスク(VHD)
- これらVHDまでのパスと、想定するドライブのUUID、そしてマウントしたいドライブレターをタプルにして格納したCSVファイル
- PowerShell Core
そして実行動作としては以下の通り。
- CSVファイルまでのパスをPowerShellスクリプトに引数で渡し、ユーザーは暗号化の解除に必要なパスワードの入力を促される。
- パスワードを入力する。
- スクリプトは、設定の内容に応じてドライブをすべてマウントする。このときのマウント操作のうち、特定のUUIDを特定のドライブレターにアサインするという、マウントポジションを設定する操作は、mountvolという別のコマンドを使わないとできないので、PowerShell側では
-NoDriveLetter
オプションを使って実行するだけにする。
実行順としてはMount()
→AssignDriveLetter()
になる。自分が作ったスクリプトの中では、ドライブをクラスとしてモデリングしていて、マウントするドライブごとにこれらのメソッドが呼び出されると考えてよい。
[void] Mount() {
if (!($this.ImageAttached())) {
Mount-DiskImage -ImagePath $this.Path -NoDriveLetter
}
}
[void] AssignDriveLetter() {
$DriveLetter = [string]$this.DriveLetterExpected + ":"
mountvol.exe $DriveLetter $this.UniqueIdExpected
if (!($this.LetterAssigned())) {
throw "Failed to assign the drive letter $DriveLetter"
}
Write-Output $this
}
- BitLockerによる暗号化を解除する。
- (任意)タスクスケジューラでこのスクリプトの実行条件をコンピューターの起動時に設定し、管理者権限のシェルで以下のコマンドが走るようにする。
pwsh.exe -Command "powershellスクリプトのフルパス CSVファイルのフルパス"
次の記事
関連投稿
Podmanで同一ネットワーク内の他のコンテナのドメイン名を解決できるようにする
Podmanで、ネットワークスタックをNetavarkに変更して、同一ネットワーク内の他のコンテナのドメイン名を解決できるようにします。 続きを読む
コメント
コメントはありません。