SharePoint Client Object Model でスクリプトリンクを登録するコード

スコープ：サイトのカスタムアクションとして登録します。

```javascript
var custScriptLinkDescription = '(カスタムアクション名を識別するための一意の文字列)';
var custScriptLinkPath = 'スクリプトリンクで読み込むスクリプトファイルのパス（/で始まるサイト相対パス）';


// コンテキスト取得
var custCtx = SP.ClientContext.get_current();

// サイト カスタムアクション取得指示
var custActions = custCtx.get_web().get_userCustomActions();

// サイト カスタムアクション取得予約
custCtx.load(custActions, 'Include(Description, Location)');

// サーバーへのクエリー実行（ここまでのコンテキストに含まれる指示予約を送信）
custCtx.executeQueryAsync(
    function (sender, args) { // クエリー実行時コールバック処理

        // 一度指定された Description のカスタムアクションをクリーンナップする --------------------------

        // 既存のカスタムアクション数を取得
        var cntCustomAction = custActions.get_count();
        if (cntCustomAction > 0) {
            // 既存のカスタムアクションがある場合
            console.log(cntCustomAction + ' custom actions exists.');

            var deletingActions = []; // 削除対象オブジェクト保持用配列

            var actionEnumerator = custActions.getEnumerator();
            while (actionEnumerator.moveNext()) {
                var action = actionEnumerator.get_current();

                if (action.get_description() == custScriptLinkDescription) {
                    // description が同じ場合
                    console.log('Custom action "' + action.get_description() + '" will be deleted.');

                    // 削除対象オブジェクト保持用配列に追加
                    deletingActions.push(action);

                } else {
                    console.log('Custom action "' + action.get_description() + '" is not target.');
                }
            }
            for (var delAction in deletingActions) {

                // 削除対象オブジェクト保持用配列の要素に対して、削除実行（Enumeratorで回している間は削除できないため）
                deletingActions[delAction].deleteObject();

            }
        } else {
            console.log('Any custom actions not exists.');
        }


        // カスタムアクション(ScriptLink)追加 ---------------------------------------------------------------------
        var newAction = custActions.add();
        newAction.set_location('ScriptLink');
        newAction.set_description(custScriptLinkDescription);
        newAction.set_sequence(0);
        newAction.set_scriptBlock(
            "var pathCustomScript" + custScriptLinkDescription + " = '" + (_spPageContextInfo.webAbsoluteUrl + custScriptLinkPath) + "?ver=' + (new Date()).toISOString().replace(/-|:|\./g,'');" + "\r"
            + "var scriptCSForAnswer = document.createElement('SCRIPT');" + "\r"
            + "scriptCSForAnswer.src = pathCustomScript" + custScriptLinkDescription + ";" + "\r"
            + "scriptCSForAnswer.type = 'text/javascript';" + "\r"
            + "document.getElementsByTagName('head')[0].appendChild(scriptCSForAnswer);" + "\r"
        );
        newAction.update();
        console.log('custom action will install.');

        custCtx.executeQueryAsync(
            function (sender, args) { // クエリー実行時コールバック処理
                console.log('Add(delete) complete!');
            },
            function (sender, args) { // クエリー失敗時コールバック処理
                console.log('Add(delete) action failed. ' + args.get_message() + '\n' + args.get_stackTrace());
            }
        );
    },
    function (sender, args) { // クエリー失敗時コールバック処理
        console.log('Get actions failed. ' + args.get_message() + '\n' + args.get_stackTrace());
    }
);
```