Hey, Scripting Guy!Windows PowerShell で .CAB ファイルを作成する

Microsoft Scripting Guys

目次

.cab ファイルを展開する
makecab.exe ユーティリティを使用する

私の好きな映画の 1 つである「JM」は、未来のデータ密輸人だった男についての映画でした。彼は、さまざまな国を訪れて顧客に会っていました。彼の頭の下部にはプラグが外科的に埋め込まれており、顧客は Zune のようなデバイスをこのプラグに接続して、大量のデータを彼の脳に転送しました。このデータ密輸人は脳をパーティション分割し、どうやら NTFS ファイルのアクセス許可を使用していたので、彼には自分の脳のデータ密輸用領域に対するアクセス許可がありませんでした。そのため、データは、密輸人自身の疑念からも保護されていました。あるシーンで、顧客は彼に大量のデータを運ぶよう依頼しましたが、密輸人にはそれほど大きなデータを運ぶ容量がありませんでした。そこで彼はどうしたと思います? 彼は自分の脳を圧縮しました。大好きなシーンです。

データ圧縮は、映画にぴったりの題材であるだけでなく、ネットワーク管理者にとっても同様に役立ちます。私たちは皆、さまざまなファイル圧縮ユーティリティを知っていて、使用しているはずです。私は毎週ファイル圧縮ユーティリティを使用して、新しい著書の数章分のデータを電子メールで Microsoft Press の編集者に送付しています。ファイル圧縮ユーティリティを使用するのは、帯域幅が制限されているからではなく、電子メールのクオータで保存、送信、および受信できる電子メール メッセージのサイズが制限されているからです。また、ラップトップに格納されているファイルを、オフィスに何枚か転がっているポータブル ディスクの 1 枚にアーカイブする必要があったときにも、ファイル圧縮ユーティリティを使用しました。

世界各地を訪れてスクリプトのワークショップを指導していたとき、私は決まって「スクリプトでファイルを圧縮する方法はありますか」という質問を受けました。そして、この質問に対して、「コマンド ライン スイッチをサポートするサードパーティ製のユーティリティを購入する必要があります」と答えていました。ある日、レジストリを眺めているときに、私は (おもしろいことに) makecab.makecab という名前の COM オブジェクトを発見しました。はて、このオブジェクトの機能は何だと思いますか。そうです。このオブジェクトを使用すると、キャビネット (.cab) ファイルを作成できます。.cab ファイルは、さまざまなアプリケーションでパッケージ化や展開に使用する効率的に圧縮されたファイルです。しかし、意欲的なネットワーク管理者やスクリプトの権威がこのようなツールを使用するのを止める方法はありません。それこそが今回のコラムの内容です。まず、図 1 のスクリプトを説明しましょう。

図 1 CreateCab.ps1

Param(
  $filepath = "C:\fso", 
  $path = "C:\fso\aCab.cab",
  [switch]$debug
  )
Function New-Cab($path,$files)
{
 $makecab = "makecab.makecab"
 Write-Debug "Creating Cab path is: $path"
 $cab = New-Object -ComObject $makecab
 if(!$?) { $(Throw "unable to create $makecab object")}
 $cab.CreateCab($path,$false,$false,$false)
 ForEach ($file in $files)
 {
 $file = $file.fullname.tostring()
 $fileName = Split-Path -path $file -leaf
 Write-Debug "Adding from $file"
 Write-Debug "File name is $fileName"
 $cab.AddFile($file,$filename)
 }
 Write-Debug "Closing cab $path"
 $cab.CloseCab()
} #end New-Cab

# *** entry point to script ***
if($debug) {$DebugPreference = "continue"}
$files = Get-ChildItem -path $filePath | Where-Object { !$_.psiscontainer }
New-Cab -path $path -files $files

CreateCab.ps1 –filepath C:\fso1

最初に、Param ステートメントを使用してコマンド ライン パラメータを作成する必要があります。Param ステートメントは、スクリプト内で、コメント行ではない最初のコード行である必要があります。スクリプトを Windows PowerShell コンソールやスクリプト エディタから実行するときには、スクリプトの実行方法を制御するためにコマンド ライン パラメータを使用します。このようにすると、異なるディレクトリで .cab ファイルを作成するたびにスクリプトを変更する必要がなくなります。必要なのは、先に示したように –filepath パラメータに新しい値を指定することだけです。

コマンド ライン パラメータの長所は、部分的なパラメータを補完する機能があることです。つまり、パラメータ全体を指定する必要はなく、パラメータが一意になるために必要なパラメータの情報を指定するだけで済みます。そのため、次のようなコマンド ライン構文を使用できます。

CreateCab.ps1 –f c:\fso1 –p c:\fso2\bcab.cab –d

この構文では c:\fso1 ディレクトリが検索され、すべてのファイルが取得されます。次に、c:\ ドライブの fso2 フォルダに bcab.cab という名前のキャビネット ファイルが作成されます。また、実行中にデバッグ情報が表示されます。–debug パラメータはスイッチ パラメータです。つまり、パラメータが指定されている場合にのみ有効です。CreateCab.ps1 の関連するセクションを次に示します。

Param(
  $filepath = "C:\fso", 
  $path = "C:\fso\aCab.cab",
  [switch]$debug
  )

今度は、New-Cab 関数を作成します。この関数では、–path と –files の 2 つの入力パラメータを受け取ります。

Function New-Cab($path,$files)

makecab.makecab というプログラム ID を $makecab という名前の変数に代入すると、スクリプトが少し読みやすくなります。ここで、1 つ目の Write-Debug ステートメントを追加することもお勧めします。

{
 $makecab = "makecab.makecab"
 Write-Debug "Creating Cab path is: $path"

次に、COM オブジェクトを作成します。

 $cab = New-Object -ComObject $makecab

簡単なエラー チェックも必要です。これは、$? 自動変数を使用して実現できます。

 if(!$?) { $(Throw "unable to create $makecab object")}

makecab.makecab オブジェクトの作成時にエラーが発生しなかった場合は、$cab 変数に格納されたオブジェクトを使用して、CreateCab メソッドを呼び出せます。

 $cab.CreateCab($path,$false,$false,$false)

.cab ファイルが作成されたら、ForEach ステートメントを使用してファイルを .cab ファイルに追加できます。

 ForEach ($file in $files)
 {
 $file = $file.fullname.tostring()
 $fileName = Split-Path -path $file -leaf

完全なファイル名を文字列に変換し、Split-Path コマンドレットを使用してディレクトリ情報を削除したら、次のように Write-Debug ステートメントを追加して、ユーザーに処理の進行状況を通知できるようにします。

 Write-Debug "Adding from $file"
 Write-Debug "File name is $fileName"

次は、ファイルを .cab ファイルに追加します。

 $cab.AddFile($file,$filename)
 }
 Write-Debug "Closing cab $path"

.cab ファイルを閉じるには、CloseCab メソッドを呼び出します。

 $cab.CloseCab()
} #end New-Cab

では、スクリプトのエントリ ポイントに移りましょう。まず、$debug 変数の存在の有無で、スクリプトがデバッグ モードで実行されているかどうかを確認します。$debug 変数が存在しない場合は、何もする必要はありません。変数が存在する場合、$DebugPreference 変数の値を continue に設定する必要があります。この設定により、Write-Debug ステートメントが画面に表示されます。既定では、$DebugPreference 変数は、SilentlyContinue に設定されています。つまり、何も実行せずにコマンドがスキップされます。コードを以下に示します。

if($debug) {$DebugPreference = "continue"}

ここで、ファイルのコレクションを取得する必要があります。この処理には、Get-ChildItem コマンドレットを使用できます。

$files = Get-ChildItem -path $filePath | 
 Where-Object { !$_.psiscontainer }

続いて、次に示すように、ファイルのコレクションを New-Cab 関数に渡します。

New-Cab -path $path -files $files

CreateCab.ps1 スクリプトをデバッグ モードで実行すると、図 2 のような出力が表示されます。

fig02.gif

図 2 CreateCab.ps1 をデバッグ モードで実行した場合の出力

.cab ファイルを展開する

makecab.makecab オブジェクトには展開メソッドがないので、.cab ファイルの展開には makecab.makecab オブジェクトを使用できません。また、makecab.expandcab オブジェクトも、存在していないので使用できません。しかし、Windows シェルには .cab ファイルの展開機能がもともと備わっているので、シェル オブジェクトを使用できます。シェルにアクセスするには、図 3 の ExpandCab.ps1 スクリプトに示すように、Shell.Application COM オブジェクトを使用できます。

図 3 ExpandCab.ps1

Param(
  $cab = "C:\fso\acab.cab",
  $destination = "C:\fso1",
  [switch]$debug
  )
Function ConvertFrom-Cab($cab,$destination)
{
 $comObject = "Shell.Application"
 Write-Debug "Creating $comObject"
 $shell = New-Object -Comobject $comObject
 if(!$?) { $(Throw "unable to create $comObject object")}
 Write-Debug "Creating source cab object for $cab"
 $sourceCab = $shell.Namespace($cab).items()
 Write-Debug "Creating destination folder object for $destination"
 $DestinationFolder = $shell.Namespace($destination)
 Write-Debug "Expanding $cab to $destination"
 $DestinationFolder.CopyHere($sourceCab)
}

# *** entry point ***
if($debug) { $debugPreference = "continue" }
ConvertFrom-Cab -cab $cab -destination $destination

まず、CreateCab.ps1 と同様に、このスクリプトでもコマンド ライン パラメータを作成します。

Param(
  $cab = "C:\fso\acab.cab",
  $destination = "C:\fso1",
  [switch]$debug
  )

次に、ConvertFrom-Cab 関数を作成します。この関数では、.cab ファイルが格納されるパラメータとファイルの展開先が格納されるパラメータの 2 つのコマンド ライン パラメータを受け取ります。

Function ConvertFrom-Cab($cab,$destination)

ここで、Shell.Application オブジェクトのインスタンスを作成します。これは、便利なメソッドが多数用意された非常に強力なオブジェクトです。図 4 に、Shell.Application オブジェクトのメンバを示します。

図 4 Shell.Application オブジェクトのメンバ
名前 メンバの種類 定義
AddToRecent メソッド void AddToRecent (Variant, string)
BrowseForFolder メソッド Folder BrowseForFolder (int, string, int, Variant)
CanStartStopService メソッド Variant CanStartStopService (string)
CascadeWindows メソッド void CascadeWindows ()
ControlPanelItem メソッド void ControlPanelItem (string)
EjectPC メソッド void EjectPC ()
Explore メソッド void Explore (Variant)
ExplorerPolicy メソッド Variant ExplorerPolicy (string)
FileRun メソッド void FileRun ()
FindComputer メソッド void FindComputer ()
FindFiles メソッド void FindFiles ()
FindPrinter メソッド void FindPrinter (string, string, string)
GetSetting メソッド bool GetSetting (int)
GetSystemInformation メソッド Variant GetSystemInformation (string)
Help メソッド void Help ()
IsRestricted メソッド int IsRestricted (string, string)
IsServiceRunning メソッド Variant IsServiceRunning (string)
MinimizeAll メソッド void MinimizeAll ()
NameSpace メソッド Folder NameSpace (Variant)
Open メソッド void Open (Variant)
RefreshMenu メソッド void RefreshMenu ()
ServiceStart メソッド Variant ServiceStart (string, Variant)
ServiceStop メソッド Variant ServiceStop (string, Variant)
SetTime メソッド void SetTime ()
ShellExecute メソッド void ShellExecute (string, Variant, Variant, Variant, Variant)
ShowBrowserBar メソッド Variant ShowBrowserBar (string, Variant)
ShutdownWindows メソッド void ShutdownWindows ()
Suspend メソッド void Suspend ()
TileHorizontally メソッド void TileHorizontally ()
TileVertically メソッド void TileVertically ()
ToggleDesktop メソッド void ToggleDesktop ()
TrayProperties メソッド void TrayProperties ()
UndoMinimizeALL メソッド void UndoMinimizeALL ()
Windows メソッド IDispatch Windows ()
WindowsSecurity メソッド void WindowsSecurity ()
Application プロパティ IDispatch Application () {get}
Parent プロパティ IDispatch Parent () {get}

COM オブジェクトの名前は何度も使用する必要があるので、COM オブジェクトのプログラム ID を変数に代入することをお勧めします。代入した文字列は、New-Object コマンドレットで使用できるだけでなく、ユーザーに結果を表示する場合にも使用できます。以下は、Shell.Application オブジェクトのプログラム ID を文字列に代入するコード行です。

{
 $comObject = "Shell.Application"

Write-Debug コマンドレットで Shell.Application オブジェクトを作成しようとしていることを示すメッセージを指定して、結果を表示できます。

 Write-Debug "Creating $comObject"

次は、実際にオブジェクトを作成します。

 $shell = New-Object -Comobject $comObject

続いて、エラーのテストをします。テストには、最後に実行したコマンドが正常に完了したかどうかを示す自動変数 $? を使用できます。この変数は、ブール型 (真/偽) です。この点を利用すると、コードを簡略化できます。not 演算子 (!) を if ステートメントと組み合わせて使用します。変数が真ではない場合は、次のように、Throw ステートメントを使用してエラーを生成し、スクリプトの実行を停止します。

 if(!$?) { $(Throw "unable to create $comObject object")}

スクリプトで正常に Shell.Application オブジェクトが作成された場合は、その結果を表示します。

 Write-Debug "Creating source cab object for $cab"

この処理の次の手順は、.cab ファイルに接続することです。ファイルに接続するには、Shell.Application オブジェクトの Namespace メソッドを使用できます。これも重要な手順なので、ユーザーに進捗状況を通知するために Write-Debug ステートメントを使用するのは理にかなっています。

 $sourceCab = $shell.Namespace($cab).items()
 Write-Debug "Creating destination folder object for $destination"

ここで、展開先フォルダに接続します。フォルダに接続するには、Namespace メソッドを使用し、また 1 つ Write-Debug ステートメントを使用して、実際の接続先フォルダをユーザーに知らせます。

 $DestinationFolder = $shell.Namespace($destination)
 Write-Debug "Expanding $cab to $destination"

ここまでの準備をすべて済ませた後では、.cab ファイルを展開するための実際のコマンドには少し拍子抜けするでしょう。このコマンドでは、$DestinationFolder 変数に格納されているフォルダ オブジェクトの CopyHere メソッドを使用します。$sourceCab 変数に格納された .cab ファイルへの参照を、入力パラメータとして次のように指定します。

 $DestinationFolder.CopyHere($sourceCab)
}

スクリプトのエントリ ポイントでは、2 つの処理を行います。まず最初に、$debug 変数が存在するかどうかを確認します。$debug 変数が存在する場合は、Write-Debug コマンドレットによってメッセージがコンソール ウィンドウに出力されるように、$debugPreference 変数に continue が設定されます。次の処理では、ConvertFrom-Cab 関数を呼び出して、.cab ファイルへのパスを –cab コマンド ライン パラメータで渡し、ファイルの展開先を –destination パラメータで渡します。

if($debug) { $debugPreference = "continue" }
ConvertFrom-Cab -cab $cab -destination $destination

ExpandCab.ps1 スクリプトをデバッグ モードで実行すると、図 5 のような出力が表示されます。

fig05.gif

図 5 ExpandCab.ps1 をデバッグ モードで実行した場合の出力

makecab.exe ユーティリティを使用する

これら 2 つのスクリプトを Windows Server 2003 または Windows XP で実行する場合はまったく問題ありませんが、Windows Vista 以降には makecab.makecab という COM オブジェクトが存在しません。しかし、決意を固めたスクリプト作成者はこのような不運でくじけたりしません。というのも、makecab.exe はコマンド ラインから常に使用できるからです。そのためには、CreateCab2.ps1 スクリプト (図 6 参照) を使用できます。

図 6 CreateCab2.ps1

Param(
  $filepath = "C:\fso", 
  $path = "C:\fso1\cabfiles",
  [switch]$debug
  )
Function New-DDF($path,$filePath)
{
 $ddfFile = Join-Path -path $filePath -childpath temp.ddf
 Write-Debug "DDF file path is $ddfFile"
 $ddfHeader =@"
;*** MakeCAB Directive file
;
.OPTION EXPLICIT      
.Set CabinetNameTemplate=Cab.*.cab
.set DiskDirectory1=C:\fso1\Cabfiles
.Set MaxDiskSize=CDROM
.Set Cabinet=on
.Set Compress=on
"@
 Write-Debug "Writing ddf file header to $ddfFile" 
 $ddfHeader | Out-File -filepath $ddfFile -force -encoding ASCII
 Write-Debug "Generating collection of files from $filePath"
 Get-ChildItem -path $filePath | 
 Where-Object { !$_.psiscontainer } |
 ForEach-Object `
 { 
 '"' + $_.fullname.tostring() + '"' | 
 Out-File -filepath $ddfFile -encoding ASCII -append
 }
 Write-Debug "ddf file is created. Calling New-Cab function"
 New-Cab($ddfFile)
} #end New-DDF

Function New-Cab($ddfFile)
{
 Write-Debug "Entering the New-Cab function. The DDF File is $ddfFile"
 if($debug)
 { makecab /f $ddfFile /V3 }
 Else
 { makecab /f $ddfFile }
} #end New-Cab

# *** entry point to script ***
if($debug) {$DebugPreference = "continue"}
New-DDF -path $path -filepath $filepath

他のスクリプトと同様、CreateCab2.ps1 では最初にコマンド ライン パラメータをいくつか作成します。

Param(
  $filepath = "C:\fso", 
  $path = "C:\fso1\cabfiles",
  [switch]$debug
  )

–debug スイッチを指定してスクリプトを実行すると、出力は図 7 のようになります。

fig07.gif

図 7 CreateCab2.ps1 をデバッグ モードで実行した場合の出力

次に、スクリプトでは New-DDF 関数を作成します。この関数では、makecab.exe プログラムで .cab ファイルを作成する際に使用される基本的な .ddf ファイルを作成します。このような種類のファイルの構文については、「Microsoft Cabinet SDK について」を参照してください。function キーワードを使用して New-DDF 関数を作成したら、Join-Path コマンドレットを使用して一時的な .ddf ファイルへのファイル パスを作成します。ドライブ、フォルダ、およびファイル名を連結することもできますが、この方法は面倒で間違いが発生しやすいので、次のように、ファイル パスを作成する際には、次のように Join-Path コマンドレットを使用することをお勧めします。

Function New-DDF($path,$filePath)
{
 $ddfFile = Join-Path -path $filePath -childpath temp.ddf

次のように Write-Debug コマンドレットを使用して、–debug スイッチを使用してスクリプトが実行されている場合に、ユーザーに簡単なフィードバックを表示できます。

 Write-Debug "DDF file path is $ddfFile"

ここで .ddf ファイルの最初の部分を作成する必要があります。そのためには、拡張 here-string を使用できます。つまり、特殊文字のエスケープに配慮する必要がなくなります。たとえば、.ddf ファイルのコメント行はセミコロンで始まっていますが、セミコロンは Windows PowerShell の予約文字です。here-string を使用せずにコメント行のテキストを作成しようとすると、すべてのセミコロンをエスケープしてコンパイル時エラーを回避する必要があります。拡張 here-string を使用すると、変数の拡張を利用できます。here-string はアット マークと引用符で開始し、引用符とアット マークで終了します。

 $ddfHeader =@"
;*** MakeCAB Directive file
;
.OPTION EXPLICIT      
.Set CabinetNameTemplate=Cab.*.cab
.set DiskDirectory1=C:\fso1\Cabfiles
.Set MaxDiskSize=CDROM
.Set Cabinet=on
.Set Compress=on
"@

次に、Write-Debug コマンドレットを使用してフィードバックを返すコードを追加することをお勧めします。

 Write-Debug "Writing ddf file header to $ddfFile" 

ここで、問題が発生する可能性のある箇所に入りました。.ddf ファイルは純粋な ASCII でなければなりません。ですが、Windows PowerShell では、既定で Unicode が使用されます。ASCII ファイルを作成するには、Out-File コマンドレットを使用する必要があります。ほとんどの場合は、ファイルのリダイレクト用矢印を使用すれば Out-File コマンドレットの使用を省略できます。ただし、今回は例外です。この構文を次に示します。

 $ddfHeader | Out-File -filepath $ddfFile -force 
-encoding ASCII

おそらく、Get-ChildItem コマンドレットを使用してファイルのコレクションを取得する前に、Write-Debug コマンドレットを使用してもう少しデバッグ情報を提供する必要があるでしょう。ということで、次のコード行を追加しました。

 Write-Debug "Generating collection of files from $filePath"
 Get-ChildItem -path $filePath | 

makecab.exe ではフォルダを圧縮できないので、フォルダをフィルタで除外することは重要です。フォルダを除外するには、Where-Object コマンドレットを not 演算子と組み合わせて使用します (not 演算子ではオブジェクトがコンテナではないことを指定しています)。

 Where-Object { !$_.psiscontainer } |

次は、各ファイルがパイプラインに追加されるたびに、そのファイルを操作する必要があります。この処理には、ForEach-Object コマンドレットを使用します。ForEach-Object は言語ステートメントではなくコマンドレットなので、ForEach-Object コマンドレットの名前と同じ行に中かっこを記述する必要があります。このコマンドレットの問題は、コード内に中かっこが埋もれやすいことです。ベスト プラクティスとして、私はコマンドが上記の Where-Object コマンドのように非常に短くない限り、中かっこの位置を調整することにしています。しかし、位置を調整するには、行継続文字 (アクサン グラーブ文字) を使用する必要があります。開発者の中には行継続文字を疫病のように避ける人もいますが、私は中かっこの位置を調整することは、コードが読みやすくなるので重要だと考えています。ForEach-Object コマンドレットの開始部分を次に示します。

 ForEach-Object `

makecab.exe で使用される .ddf ファイルは ASCII テキストなので、Get-ChildItem コマンドレットから返される System.IO.Fileinfo オブジェクトの fullname プロパティの値を文字列に変換する必要があります。また、名前にスペースが含まれたファイルが存在する場合があるので、ファイルの fullname プロパティの値は引用符で囲むのが妥当でしょう。

 { 
 '"' + $_.fullname.tostring() + '"' | 

続いてファイル名を Out-File コマンドレットにパイプします。ここでは ASCII エンコーディングを指定し、テキスト ファイルの既存内容を上書きしないように –append スイッチを使用します。

 Out-File -filepath $ddfFile -encoding ASCII -append
 }

これで、デバッグ モードで実行しているユーザーに更新情報を提供し、New-Cab 関数を呼び出すことができます。

 Write-Debug "ddf file is created. Calling New-Cab function"
 New-Cab($ddfFile)
} #end New-DDF

また、New-Cab 関数の処理が開始されたら、開始されたことをユーザーに通知できます。

Function New-Cab($ddfFile)
{
 Write-Debug "Entering the New-Cab function. The DDF File is $ddfFile"

次に、–debug スイッチを指定してスクリプトを実行している場合は、makecab.exe の /V パラメータを使用して、詳細なデバッグ情報を表示できます (3 は完全な詳細を出力し、0 は出力なしです)。–debug スイッチを指定してスクリプトが実行されていない場合は、大量の情報で画面が乱雑にならないよう、ユーティリティの既定値を使用することをお勧めします。

 if($debug)
 { makecab /f $ddfFile /V3 }
 Else
 { makecab /f $ddfFile }
} #end New-Cab

スクリプトのエントリ ポイントでは、$debug 変数が存在しているかどうかを確認します。$debug 変数が存在している場合は、$debugPreference 自動変数の値は continue に設定され、Write-Debug コマンドレットによってデバッグ情報が表示されます。この変数の存在の有無を確認したら、コマンド ラインで指定された 2 つの値 (path および filepath) を使用して、New-DDF コマンドレットが呼び出されます。

if($debug) {$DebugPreference = "continue"}
New-DDF -path $path -filepath $filepath

以上で、ファイルの圧縮と展開に関する今月のコラムは終わりです。頭が爆発しそうだと思われる方は、Zune のプラグを耳に差し込み、上記のスクリプトのいずれかを実行して、頭の中を圧縮できるかどうかお試しください。ただし、この方法は映画の中でしか役に立ちません。申し訳ありません。フリーズしなかった方は、TechNet スクリプト センターにアクセスして、Hey, Scripting Guy! の日刊コラムもご覧ください。そちらでまたお会いしましょう。

Ed Wilson は有名なスクリプトの専門家です。『Windows PowerShell Scripting Guide』(2008 年)、『Microsoft Windows PowerShell Step by Step』(2007 年) など、8 冊の書籍を執筆しています。Ed は、マイクロソフト認定システム エンジニア (MCSE) や情報システム セキュリティ プロフェッショナル (CISSP) など、20 種類を超えるこの業界の資格を持っています。また、余暇には、木工作業、水中写真撮影、スキューバ ダイビング、お茶などを楽しんでいます。

Craig Liebendorfer は言葉を巧みに操る、マイクロソフトのベテラン Web 編集者です。Craig は毎日言葉にかかわって給料を受け取ることができる仕事が存在することが、いまだに信じられないと思っています。彼が大好きなことの 1 つは場違いなユーモアなので、まさにこのコラムにうってつけの人物です。彼は自分の立派な娘が、自分の人生における最も偉大な功績であると考えています。