sanpobiyori.info

PowerShellで並列処理して遊ぶ

相変わらず、PowerShellの貝殻本こと『PowerShell実践ガイドブック』を少しずつ読み進めている今日この頃ですが、PowerShellのジョブ実行について書かれているところを読んでいて、以前から少しやってみたかったことを思い出したのでやってみました。

やりたかったことというのは、SharePoint Onlineのカスタムリストへ対し情報を書き込む作業を並列処理で行うというものです。

まず並列処理に入る前に、SharePoint Onlineのカスタムリストへの書き込む処理は以下のコードで実現できます。

もしお手元の環境で実行される場合は、事前にモジュールのインストールを行ってください。

$SiteUrl = "https://xxxxx.sharepoint.com/sites/xxxxx"
$ListName = "<CustomList Title>"
$UserName  = "<Account MailAddress>"
$Password = Read-Host -Prompt "Enter Password" -AsSecureString

Import-Module Microsoft.Online.SharePoint.PowerShell -DisableNameChecking

$Context = New-Object Microsoft.SharePoint.Client.ClientContext($SiteUrl)
$Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($UserName, $Password)
$Context.Credentials = $Credentials
$Context.RequestTimeOut = 5000 * 60 * 10;
$Web = $Context.Web
$List = $Web.Lists.GetByTitle($ListName)
$Context.Load($List)
$Context.ExecuteQuery()

1..10 | %{
    $ListItemInfo = New-Object Microsoft.SharePoint.Client.ListItemCreationInformation
    $Item = $List.AddItem($ListItemInfo)
    $Title = $Prefix * 1000 + [int]$_
    $Item["Title"] = $Title
    $Item.Update()
    $Context.ExecuteQuery()
}

Write-Output "done"

このコードをベースに「Start-Job」コマンドレットを使って並列化を行うわけですが、色々失敗しました。

例えばこんな具合

Import-Module Microsoft.Online.SharePoint.PowerShell -DisableNameChecking

$Context = New-Object Microsoft.SharePoint.Client.ClientContext($SiteUrl)
$Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($UserName, $Password)
$Context.Credentials = $Credentials
$Context.RequestTimeOut = 5000 * 60 * 10;
$Web = $Context.Web
$List = $Web.Lists.GetByTitle($ListName)
$Context.Load($List)
$Context.ExecuteQuery()

Functions = {
    function Write-CustomList
    {
        param (
            $Prefix
            ,$Context
        )

        1..10 | %{
            $ListItemInfo = New-Object Microsoft.SharePoint.Client.ListItemCreationInformation
            $Item = $List.AddItem($ListItemInfo)
            $Title = $Prefix * 1000 + [int]$_
            $Item["Title"] = $Title
            $Item.Update()
            $Context.ExecuteQuery()

            sleep -Seconds 5
        }
    }
}

$Job = 1..5 | %{ 
    Start-Job Write-CustomList $_ $Context
}

Wait-Job -Job $Job
Remove-Job -Job $Job

Write-Output "done"

色々ダメダメですね(笑

「Start-Job」コマンドレットを使った並列化を行う場合、新規プロセスが作成される点に気を付ける必要があるのですが、その点が全然考慮されていません。

では、具体的にはどういった点に気を付ける必要があるかというと

  • 変数や関数は新しく発行されたプロセスに引き継がれない
  • スクリプトの実行パスは既定の場所が使用される

今回、スクリプトの実行パスは関係ありませんが、変数や関数が引き継がれないという点が先ほどのスクリプトでは考慮されていないことが分かります。

また、変数や関数が引き継がれないということは、読み込んだモジュールやSharePoint Onlineとのセッション情報も引き継がれないということになるので、その点も考慮する必要があります。

これらの点を考慮しつつ並列化すると、以下のスクリプトとなります。

$SiteUrl = "https://xxxxx.sharepoint.com/sites/xxxxx"
$ListName = "<CustomList Title>"
$UserName  = "<Account MailAddress>"
$Password = Read-Host -Prompt "Enter Password" -AsSecureString

$LogonData = @{
    'SiteUrl' = $SiteUrl;
    'ListName' = $ListName;
    'UserName' = $UserName;
    'Password' = $Password
}

$Functions = {
    function Write-CustomList
    {
        param (
            $Prefix
            ,$LogonData
        )

        $SiteUrl = $LogonData['SiteUrl']
        $ListName = $LogonData['ListName']
        $UserName = $LogonData['UserName']
        $Password = $LogonData['Password']

        Import-Module Microsoft.Online.SharePoint.PowerShell -DisableNameChecking

        $Context = New-Object Microsoft.SharePoint.Client.ClientContext($SiteUrl)
        $Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($UserName, $Password)
        $Context.Credentials = $Credentials
        $Context.RequestTimeOut = 5000 * 60 * 10;
        $Web = $Context.Web
        $List = $Web.Lists.GetByTitle($ListName)
        $Context.Load($List)
        $Context.ExecuteQuery()

        1..10 | %{
            $ListItemInfo = New-Object Microsoft.SharePoint.Client.ListItemCreationInformation
            $Item = $List.AddItem($ListItemInfo)
            $Title = $Prefix * 1000 + [int]$_
            $Item["Title"] = $Title
            $Item.Update()
            $Context.ExecuteQuery()

            sleep -Seconds 5
        }
    }
}

$Job = 1..5 | %{ 
    Start-Job -InitializationScript $Functions `
        -ScriptBlock{
            param ($Prefix, $LogonData)
            Write-CustomList $Prefix $LogonData
        } `
        -ArgumentList $_ , $LogonData
}
Wait-Job -Job $Job
Remove-Job -Job $Job

Write-Output "done"

実行結果がこちら

network-image

正しく並列化されていることが分かりますね。

実際に使うとなった場合、入る順番が順不同となる可能性が高いため、そのあたりを考慮に入れる必要があるかと思いますが、マシンスペックと回線速度次第では、ある程度まとまった量のデータをSharePoint Onlineに書き込む必要がある場合でも、大幅な時間短縮ができそうです。

ただし、並列化のし過ぎでSharePoint Online側からセッションが切られる可能性はあるので程々にですが・・・。

また、基本的なSharePoint Serverでもある程度参考にはなるかと思いますが、こちらの場合はサーバスペックの問題があるので、現実的にはあまり並列化はできないと思われます。

コードはGitHubでも公開しています。

余談

今更ながらGitHubに初めてコードを登録しました。

今まで興味はあったのですが、そういった機会やアップロードするようなコードもなかったので利用を後回しにしていたのですが、これからはなるべく積極的に使用して、使い方を覚えたいですね。

参考URL

comments powered by Disqus