PowershellからBITS(Background Intelligent Transfer Service)を使用して大容量ファイルを配布する

BITSはMicrosoftがWindowsに標準機能として載せている分散ダウンロード機能です。WindowsUpdateもバックグラウンドでBITSを利用しており、LAN内の複数のPCからWindowsUpdateをダウンロードする場合には、他のパソコンが自動的にキャッシュサーバーとなることで、インターネットとの通信負荷を押させてくれます。このBITSはWindowsUpdate専用の機能というわけではなく、簡単なプログラムを用意すれば、大容量ファイルを配布するときに自由に活用することができます。

昨今、プログラムやセキュリティパッチのフットプリント(ファイルサイズ)が大きく鳴り続ける、パッチ配布のネットワーク負荷が原因でインターネットが輻輳するなんて事件もありましたね。社内ネットワーク(WAN)はその構造上、どうしても一か所に負荷が集中しやすく、分散ダウンロードができると随分と助かりますね。

BITSでダウンロードするための一連の流れは次のようになります。

  1. HTTPサーバー上にダウンロード元となるファイルを用意します。
  2. Start-BitsTransferでBITSに新しいダウンロード要求を登録します。
  3. 定期的にGet-BitsTransferを呼び出しダウンロードの完了を待ちます。
  4. ダウンロード完了後にComplete-BitsTransferでファイルに書き出します。

上記をPowershell Scriptで記述したのが下記です。このスクリプトをダウンロードが完了するまで定周期で実行します。

$displayName = 'BITS_Sample'; # BITSにダウンロード要求を登録する時の表示名
$fromURL = 'http://www.example.co.jp/BITS_Sample.zip'; # ダウンロード元のURL
$destFile = 'C:\TEMP\BITS_Sample.zip'; # ダウンロード先のファイル名
$logFile = 'C:\TEMP\BITS_Sample.log' # ログ出力先のファイル名

$noBitsInstance = $true;
$completeDownload = $false;

Add-Content -Path $logFile -Value ('Start Script:' + (Get-Date));

# ダウンロード先フォルダが無ければ作成しておく
if ($false -eq (Test-Path 'C:\TEMP')){
    mkdir 'C:\TEMP';
}

# ダウンロードファイルが
if ($false -eq (Test-Path $destFile)){
    # BITSへのダウンロード要求を列挙する
    Get-BitsTransfer | Where-Object {
        Add-Content -Path $logFile -Value ('BITS Status:' + $_.DisplayName + '-' + $_.JobState);
        # 表示名の一致しているダウンロード要求が転送終了になるまで待機
        if ($_.DisplayName -eq $displayName){
            $noBitsInstance = $false;
            if ($_.JobState -eq "Transferred") {
         # ダウンロード完了した転送要求を完了させる
                Complete-BitsTransfer $_;
                $completeDownload = $true;
            }
        }
    }

    # BITSにダウンロード要求が登録されていなければ、新たに登録する。
    if ($noBitsInstance -eq $true){
        $delayMinute = Get-Random -Maximum 240;
        $kickDateTime = (Get-Date).AddMinutes($delayMinute);

        # 新規ダウンロード登録までランダムに待機する
        Add-Content -Path $logFile -Value ('Wait ' + $delayMinute + ' Minutes');
        While ($kickDateTime -ge (Get-Date)){
            Add-Content -Path $logFile -Value ('delay - ' + (Get-Date));
            sleep 60;
        }

        # 新規にダウンロードを登録する
        Add-Content -Path $logFile -Value ('Start BitsTransfer:' + $displayName + '-' + $destFile);
        Start-BitsTransfer -Source $fromURL -Destination $destFile -Asynchronous -Priority Normal -DisplayName $displayName
    }

    if ($completeDownload -eq $true){
        # ダウンロード完了後の処理
        Add-Content -Path $logFile -Value ('Complte Download:' + $displayName + '-' + $destFile);
    }
}
Add-Content -Path $logFile -Value ('End Script:' + (Get-Date));

私はActive Directoryのグループポリシーでログオンスクリプトとして登録しました。コントロールパネルのタスクで定周期に起動してもよいでしょう。

BITSで使用する帯域の制限などはレジストリに記述するか、ActiveDirectoryのグループポリシーで定義します。

Powershellでパスワードを指定して共有ドライブをマウントする

PowerShellからパソコンの共有ドライブをユーザーIDとパスワードを指定してマウントします。

$computerName = '192.168.1.1'; # 接続先クライアントのIPアドレス
$adminPass = 'password'; # 接続時に使用するパスワード
$adminUser = 'admin'; # 接続時に使用するユーザーアカウント

# 認証情報のインスタンスを生成する
$securePass = ConvertTo-SecureString $adminPass -AsPlainText -Force;
$cred = New-Object System.Management.Automation.PSCredential "$computerName\$adminUser", $securePass;

# 共有ドライブをBドライブとしてマウントします
New-PSDrive -Name 'B' -PSProvider FileSystem -Root "\\$computerName\C`$" -Credential $cred;

# --- ここで色々処理

# Bドライブをマウント解除します
Remove-PSDrive -Name 'B';

ちなみにC$はWindowsでデフォルトで作成される共有名です。管理者権限を持つユーザーでしか開けませんが、C:ドライブ全体へのフルアクセスになっています。ここをマウントしてゴニョゴニョできると、クライアントパソコンの諸々の管理や情報収集が捗ります。

Windowsで管理者権限のユーザーアカウントにパスワード設定しないのが論外な理由は、こういう所にある。

ActiveDirectory(以下AD)を導入してたら、ADの管理者アカウントでプロセスを走らせれば、認証情報作ル必要も無くなります。New-PSDriveすら必要なく「\\192.168.1.1\c$」でアクセス出来るので、こんなKnow Howいらないんだけどね。

UI Automation PowerShell Extensionsを使ってみる

単票入力画面で入力効率アップのための工夫が一切無く、ほぼ同じ内容を何度も入力するという非人間的な作業に追われている事務担当者というのは、私以外にも結構居るかと思う。清く正しいエンジニア兼業事務担当者としては、ここは自動化すべきだろう。というわけで、PowerShellとUI Automation PowerShell Extensionsを使って作業の自動化を試みるよ。

UI Automation PowerShell ExtensionsはVisual Studioで開発されたアプリケーション画面のテストを自動化するために提供されているMicrosoft UI AutomationをPowerShellから呼び出せるようにしたものです。

UI Automation PowerShell Extensionsには残念な制約があって、Powershell Version 2.0でないと動きません。もしより高いバージョンのPowershellが既にインストールされている場合は”powershell -version 2″とコマンドライン引数を指定して、低いバージョンで起動する必要があります。また動作には管理者権限が必要です。管理者権限を持つユーザーでログインするのは当然として、UACを有効にしている場合は「管理者として実行」を選択する必要があります。

まずはUI Automation PowerShell ExtensionsからUIAutomation.x.x.xxx.NET35.zipをダウンロードします。ダウンロードしたZIPファイルに含まれているファイルを適当なフォルダに解凍します。ここでは「C:\Program Files\WindowsPowerShell\Modules」に「UIAutomation」というフォルダを作ってそこに解凍することにします。

とりあえずコマンドプロンプトからメモ帳を起動して実験してみましょう。

PS C:\Windows\system32> Import-Module "C:\Program Files\WindowsPowerShell\Modules\UIAutomation\UIAutomation.dll"
PS C:\Windows\system32> notepad
PS C:\Windows\system32> $window = Get-UiaWindow -Name '無題 - メモ帳'
PS C:\Windows\system32> $window.Keyboard.TypeText("Hello UI Automation")

メモ帳を起動して適当な文字列を入力することが出来ました。
notepad-sample

実際にScriptを作るにはUIAutomationSpy.exeと言うプログラムが付属しています。これを起動して一連の操作を行うと、その操作をScriptに保存する事が出来ます。
UIAutomation
UIAutomationSpy.exeを起動してStartボタンをクリックした後、Scriptにしたい一連の操作を実施し、Stopボタンをクリックします。Scriptタブに移動すると先ほどの操作を再現するためのScriptが生成されています。これをテキストエディタに張り付けて、Powershell Scriptとして保存します・・・が、この自動生成されるScriptはかなり汚いので実際に利用の際には大幅に修正する事になります。例えば先ほどのメモ帳に入力するサンプルも自動生成したScriptだとこうなります。

Get-UiaWindow -Class 'Notepad' -Name '無題 - メモ帳' | `
Get-UiaDocument -AutomationId '15' -Class 'Edit' -Name 'H'

Get-UiaWindow -Class 'Notepad' -Name '無題 - メモ帳' | `
Get-UiaDocument -AutomationId '15' -Class 'Edit' -Name 'Hell'

Get-UiaWindow -Class 'Notepad' -Name '無題 - メモ帳' | `
Get-UiaDocument -AutomationId '15' -Class 'Edit' -Name 'Hello '

Get-UiaWindow -Class 'Notepad' -Name '無題 - メモ帳' | `
Get-UiaDocument -AutomationId '15' -Class 'Edit' -Name 'Hello U'

Get-UiaWindow -Class 'Notepad' -Name '無題 - メモ帳' | `
Get-UiaDocument -AutomationId '15' -Class 'Edit' -Name 'Hello UI A'

Get-UiaWindow -Class 'Notepad' -Name '無題 - メモ帳' | `
Get-UiaDocument -AutomationId '15' -Class 'Edit' -Name 'Hello UI Au'

Get-UiaWindow -Class 'Notepad' -Name '無題 - メモ帳' | `
Get-UiaDocument -AutomationId '15' -Class 'Edit' -Name 'Hello UI Automati'

Get-UiaWindow -Class 'Notepad' -Name '無題 - メモ帳' | `
Get-UiaDocument -AutomationId '15' -Class 'Edit' -Name 'Hello UI Automation'

・・・(´・ω・`)
「AutomationId」とかClassを調べるためのツールと割り切った方が良さそうですね。

Windows7でWindows PowerShellからタスクを追加するには

Windows7でWindows PowerShellからタスクを追加するにはschtasks.exeを呼び出す以外にないらしい。

Windows PowerShellでタスクの設定を追加するにあたって、Scheduled Tasks Cmdletsを使いたかったのだけど、現状ではWindwos 8.1とWindows 2012 R2以降でしかサポートしません。

昔はWindows7でも後からインストールできた用で、PowerShell V2.0の場合はPowerShell Packをインストールすることで、PowerShell V3.0であれば標準でタスクスケジューラの制御の為のScheduledTask Cmdletを提供していたようです。現在ではPowerShell Packもダウンロード出来ません。最新のPowerShell V4.0ではWindows8.1以降かWindows 2012R2以降でなければScheduled Tasks Cmdletsをサポートしないので仕方ありませんね