からめもぶろぐ。

CSOM 完全に理解した

New-SPOSite した直後に更新操作をしようとすると失敗することがある

SharePoint Online Management Shell を使って New-SPOSite したあとに、CSOM などで何らかの更新操作 (例えばサイト グループを作ったりするなど) をしようとするとエラーになることがあります。

そもそも、SharePoint Online でのサイト コレクションの操作は時間がかかります。完全にタイミングによりなのですが、運が悪いと 30 分以上かかることもあります。SharePoint Online Management Shell では、操作が完了するまで定期的に状況をポーリングします。*1
CSOM を使う場合でも、同様のサンプル コードが掲示されています。

docs.microsoft.com

しかし実際にはこれだけでは不足していて、操作が完了したという結果が返ってきても、サイト コレクションの内部ステータスが Active になっていないことがあります。なので、New-SPOSite のあとに続けて操作を行いたい場合は、ステータスが Active になるまでさらに待つ必要があります。

$url = 'https://example.sharepoint.com/sites/site1'
$title = 'Site1'
$owner = 'admin@example.onmicrosoft.com' 
New-SPOSite -Url $url -Title $title -Owner $owner -StorageQuota 26214400 -LocaleId 1041
while ($true) {
    Start-Sleep -Seconds 5
    if ((Get-SPOSite -Identity $url).Status -eq 'Active') {
        break;
    }
}

*1:操作の完了を待たなくていい場合は -NoWait を指定することもできます

Powershell と CSOM で Geolocation 列に値を入れるときにちょっとハマったのでメモ

相変わらず PowerShell なんですけれども。

Geolocation 列に値を入れる方法については以下にまとまっています。FieldGeolocationValue を使ってねということのようです。

docs.microsoft.com

それでは実際にやってみます。

$item = New-Object Microsoft.SharePoint.Client.ListItemCreationInformation
$item = $list.AddItem($item)

$value = New-Object Microsoft.SharePoint.Client.FieldGeolocationValue
$value.Latitude = 10
$value.Longitude = 10
$item["Location"] = $value

$item.Update()
$ctx.Load($item)
$ctx.ExecuteQuery()

実行すると…?

"0" 個の引数を指定して "ExecuteQuery" を呼び出し中に例外が発生しました: "The geolocation value does not represent a geo graphical point. Either the value is not in the correct format or it is not in the valid range."
発生場所 行:1 文字:1
+ $ctx.ExecuteQuery()
+ ~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ServerException

正解はこっち。値を明示的に指定してあげる必要があります。

$item = New-Object Microsoft.SharePoint.Client.ListItemCreationInformation
$item = $list.AddItem($item)

$value = New-Object Microsoft.SharePoint.Client.FieldGeolocationValue
$value.Latitude = 10
$value.Longitude = 10
$item["Location"] = [Microsoft.SharePoint.Client.FieldGeolocationValue]$value

$item.Update()
$ctx.Load($item)
$ctx.ExecuteQuery()

ちなみに FieldLookupValue や FieldUserValue でも同じ現象が発生します。もっともこちらは ID を指定して更新できるので使わないという手もありますけど。

platyPS で PowerShell ヘルプ ファイルを簡単に書く

PowerShell チームから提供されている platyPS というツールがあります。

github.com

ざっくりいうと、既存のモジュールを読み込んで Markdown 形式のテンプレートを生成し、さらに Markdown ファイルを読み込んで PowerShell ヘルプ ファイル (dll-Help.xml) を生成してくれるというものです。ドキュメントを Markdown 形式で管理できるので、ドキュメントの記述が容易ですし、GitHub に公開しておけばそのままオンライン ドキュメントとしても使うことができます。

Markdown ファイルを作成する

こんなコマンドレットを作ったとして。

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Text;

namespace SampleModule
{
    [Cmdlet("Write", "HelloWorld")]
    public class WriteHelloWorldCommand : PSCmdlet
    {
        protected override void ProcessRecord()
        {
            this.WriteObject(JsonConvert.SerializeObject(new { Message = "Hello World" }));
        }
    }
}

対象のモジュールをインポートしてから New-MarkdownHelp を呼び出すとコマンドレットごとに Markdown ファイルを生成してくれます。

Import-Module -Name '.\bin\Debug\SampleModule.psd1'
New-MarkdownHelp -Module 'SampleModule' -OutputFolder '.\documents\'

生成された Markdown ファイルにはモジュールから読み取った情報が自動的に埋め込まれているものの、説明などは含まれていません。{{ }} で囲まれている箇所について修正していきます。

---
external help file: SampleModule.dll-Help.xml
Module Name: SampleModule
online version:
schema: 2.0.0
---

# Write-HelloWorld

## SYNOPSIS
{{Fill in the Synopsis}}

## SYNTAX

```
Write-HelloWorld [<CommonParameters>]
```

## DESCRIPTION
{{Fill in the Description}}

## EXAMPLES

### Example 1
```powershell
PS C:\> {{ Add example code here }}
```

{{ Add example description here }}

## PARAMETERS

### CommonParameters
This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable.
For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216).

## INPUTS

### None

## OUTPUTS

### System.Object
## NOTES

## RELATED LINKS

ヘルプ ファイルを作成する

修正が終わったら Markdown ファイルがあるフォルダーを指定して New-ExternalHelp を呼び出してヘルプ ファイルを生成します。

New-ExternalHelp -Path '.\documents\' -OutputPath '.\bin\Debug\SampleModule.dll-Help.xml'

これだけです。簡単ですね。

コマンドレットの変更を Markdown に反映する

開発を進めていくうちにコマンドレットに新しいパラメーターが増えたりすることもあると思います。そのような場合は Update-MarkdownHelp を呼び出すといい感じに差分を更新してくれます。*1

Import-Module -Name '.\bin\Debug\SampleModule.psd1'
Update-MarkdownHelp -Path '.\documents\'

修正したらまた New-ExternalHelp でヘルプ ファイルを再生成します。なお既存のファイルが存在すると上書きされないので -Force を付けるのを忘れずに。

*1:New-MarkdownHelp と Update-MarkdownHelp で微妙にパラメーターが違うので注意。どうしてこうなった。

AppVeyor で PowerShell Core モジュールのバージョンを書き換える

こんな記事を書いてました。

blog.karamem0.jp

これでもよかったのですが、せっかくなのでビルドするごとにバージョン番号を書き換えたいよね、ということで。

AppVeyor にはビルド時にアセンブリ バージョンを書き換える機能が提供されています。.NET Framework だけではなく .NET Core にも対応しています。詳しくは公式のドキュメントを見るのがいいと思います。

www.appveyor.com

アセンブリ バージョンについてはそれでいいのですが、PowerShell Core モジュールの場合、psd1 ファイルも書き換えてあげる必要があります。こちらはさすがに自力でやらないといけないので、ビルド スクリプトを書いてみます。

dotnet restore --source https://api.nuget.org/v3/index.json
dotnet publish --configuration Release
Update-ModuleManifest -Path "${env:APPVEYOR_BUILD_FOLDER}\bin\Release\netcoreapp2.1\publish\${env:APPVEYOR_PROJECT_NAME}.psd1" -ModuleVersion $env:APPVEYOR_BUILD_VERSION

ファイル パスは環境に合わせて適宜書き換えてみてください。Update-ModuleManifest で既存の psd1 ファイルの内容を更新できますので、AppVeyor の環境変数からバージョン番号を受け取って設定してあげます。例えば appveyor.yml ファイルに以下のように書いておけば、マイナー バージョンやメジャー バージョンを上げるときにも一元管理できます。

version: '1.0.0.{build}'

そういえば、まったくの余談ですが、PowerShell Core のライブラリは NuGet からも入手できるようになってたんですね。

www.nuget.org

SharePoint Online Management Shell が PowerShell Gallery から取得できる件

以前にこんな記事を書きました。

blog.karamem0.jp

その後 SharePoint Online Management Shell については PowerShell Gallery からもインストールできるようになったようです。半年近く前の話なのに全然気付きませんした…。

techcommunity.microsoft.com

インストーラー版の SharePoint Online Management Shell は GAC を汚染するので、特別な事情がなければ、なるべく PowerShell Gallery (Install-Module) で取得したほうがいいですね。