> For the complete documentation index, see [llms.txt](https://docs.nexthink.com/platform/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.nexthink.com/platform/ja/user-guide/remote-actions/setting-up-and-managing-remote-actions/creating-remote-actions/writing-scripts-for-remote-actions-on-windows.md).

# Windows 上でリモートアクション用スクリプトを作成する

{% hint style="info" %}
この記事で説明している操作の実行にサポートが必要な場合は、Nexthink 認定パートナーにお問い合わせください。
{% endhint %}

この記事では、Windows 上で Nexthink のリモートアクション スクリプトを準備する手順について詳しく説明します。 スクリプトは Microsoft のスクリプト言語である PowerShell（Windows .NET Framework 上に構築）で記述され、その後セキュリティ確保のために証明書で署名されます。 PowerShell スクリプトはタスクの自動化や構成管理に適しており、従業員デバイス上でリモートアクションを実行できるようにします。

{% hint style="warning" %}
デバイスが PowerShell の Constrained Language Mode で実行されている場合、リモートアクションはサポートされません。 このモードでは多くのコア機能がブロックされるため、ほとんどのアクションは想定どおりに実行されません。

Nexthink では、Full Language Mode でリモートアクションを実行することを推奨しています。
{% endhint %}

リモートアクションの主なユースケースには、デバイスからのオンデマンドデータ収集、自己修復タスク、構成設定の変更などがあります。

この記事では、読者が PowerShell スクリプトに精通していることを前提としています。

{% hint style="info" %}
スクリプトを安全に実行する方法については、Nexthink Security の [Remote Actions security best practices](https://docs.nexthink.com/security/security-best-practices/remote-actions-security-best-practices) を参照してください。
{% endhint %}

## スクリプトの作成

### 汎用スクリプトと入力変数

署名済みスクリプトのカスタマイズが必要な状況では、汎用スクリプトが便利です。 署名済みスクリプトを修正すると署名が無効になりますが、汎用スクリプトはパラメーターを使用してカスタマイズでき、署名を維持できます。

PowerShell スクリプトの先頭で形式パラメーターを宣言し、スクリプトを汎用化します。 関連するリモートアクションを編集する際、パラメーター値は Nexthink Web インターフェイスで変更できます。

たとえば、汎用的なレジストリキーを読み込むスクリプトを作成するには、レジストリ内のキーへのパスを含むパラメーターをスクリプト内で宣言します。 複数のリモートアクションが同じスクリプトを使用し、異なるレジストリキーを読み込むことができます。その場合、スクリプト内のパラメーターに異なるパスを指定します。

```
param(
    [string]$filePath,
    [string]$regPath
)
```

リモートアクション構成時にスクリプトをアップロードすると、システムはインポートされた PowerShell スクリプトのパラメーターを認識し、**パラメーター** セクションに一覧表示します。 各パラメーター名の右側に表示されるテキスト入力欄に実際の値を入力します。

{% hint style="info" %}
実際の値は常にテキストとしてスクリプトに渡されます。スクリプトで **string** 以外の型のパラメーターを宣言している場合は、スクリプトが期待される型に変換できる値を指定してください。
{% endhint %}

### 出力変数の作成

スクリプトの実行により、オンデマンドデータとして保存したい出力が生成される場合があります。 Nexthink は .NET アセンブリ（`nxtremoteactions.dll`）を提供しており、これは Collector と同時に従業員デバイスにインストールされます。 このアセンブリには **Nxt** クラスが含まれており、結果をデータレイヤーに書き込むためのメソッドを提供します。

**Nxt** クラスを使用するには、リモートアクション用 PowerShell スクリプトの先頭に次の行を追加します。

```
Add-Type -Path $env:NEXTHINK\RemoteActions\nxtremoteactions.dll
```

Nxt クラスのメソッドを使用して、必要な出力を書き込みます。 すべての書き込みメソッドは、出力名と書き込む値の 2 つの引数を受け取ります。 たとえば、ファイルサイズをデータレイヤーに書き込む場合:

```
[Nxt]::WriteOutputSize("FileSize", $fileSize)
```

リモートアクション構成時にスクリプトをアップロードすると、システムはスクリプト内の出力書き込み呼び出しを認識し、スクリプト本文下の **出力** セクションに出力変数を一覧表示します。 調査やメトリクスで参照できるように、出力のラベルを設定します。

各書き込みメソッドの末尾は、出力の種類を示します。 利用可能なメソッドと、書き込む値に対応する PowerShell 型の一覧は次の表で確認できます。

| Nxt 書き込みメソッド          | PowerShell 型 | 制約                                                           |
| --------------------- | ------------ | ------------------------------------------------------------ |
| WriteOutputString     | \[文字列]       | 0 ～ 1024 バイト（大きい場合は出力が切り捨てられます）                              |
| WriteOutputBool       | \[bool]      | true / false                                                 |
| WriteOutputUInt32     | \[uint32]    | <ul><li>最小: 0</li><li>最大: 4 294 967 295</li></ul>            |
| WriteOutputFloat      | \[float]     | <ul><li>最小: -3.4E+38</li><li>最大: 3.4E+38</li></ul>           |
| WriteOutputSize       | \[float]     | <ul><li>最小: 0</li><li>最大: 3.4E+38</li></ul>                  |
| WriteOutputRatio      | \[float]     |                                                              |
| WriteOutputBitRate    | \[float]     |                                                              |
| WriteOutputDateTime   | \[DateTime]  | DD.MM.YYYY\@HH:MM                                            |
| WriteOutputDuration   | \[TimeSpan]  | <ul><li>最小: 0 ms</li><li>最大: 49 日</li><li>ミリ秒単位の精度</li></ul> |
| WriteOutputStringList | \[string\[]] | string と同様                                                   |

### 出力フィールドの定義

スクリプトを作成するときは、必ず次の点を確認してください:

* **すべての出力フィールド名を事前に定義する:** これにより、スクリプトの実行中に出力テーブルの正しいフィールドが確実に入力されます。 predefined な field 名がない場合、script は output schema が不明なため失敗する可能性があります。
  * 出力フィールド名は常に`string`形式である必要があります。
  * 例: `[Nxt]::WriteOutputString('Output_Field_Name', $Output_Value)`
* **出力フィールドの数を定義する:** スクリプトでは常に、固定数の出力フィールドを指定する必要があります。 固定スキーマにより、結果の予測可能性と互換性が確保されます。
  * 動的なフィールドを避けてください。 動的な出力構造は、プラットフォームにおける不整合や処理エラーを引き起こす可能性があります。
  * スクリプトを実行する際に、出力フィールドを定義するためにループを使用することは避けてください。

### キャンペーンの実装

リモートアクションとキャンペーンを組み合わせて、従業員が自立して問題を解決できるようにします。 キャンペーンを使用すると、問題が検出されたことを従業員に通知し、その解決に向けて案内できます。

デバイスを操作している従業員のデスクトップにキャンペーンを表示するには：

* キャンペーンには **リモートアクション** のトリガーがあり、公開されている必要があります。
* リモートアクションのスクリプトは、次のいずれかの方法で実行できます。
  * アクションに特別な権限が不要な場合は、従業員のコンテキストで実行されます。
  * ローカル システム アカウントのコンテキストで、アクションに管理者特権が必要な場合。

### キャンペーン識別子の取得

リモートアクションからキャンペーンを実行するためのメソッドでは、引数としてキャンペーン識別子を渡す必要があります。 キャンペーンの NQL ID（推奨）とキャンペーンの UID（従来のオプション）の両方を使用できます。

{% hint style="info" %}
NQL ID を識別子として使用するには、Collector バージョン 23.5 以降が必要です。
{% endhint %}

キャンペーン識別子をリモートアクションに渡すには、必要な各キャンペーンごとに、リモートアクションのスクリプト内でパラメーターを宣言します。 リモートアクションを編集する際、パラメーターの実際の値として NQL ID（または UID）を使用します。

NQL ID またはキャンペーンの UID を取得する方法については、[Triggering a campaign](/platform/ja/user-guide/campaigns/campaigns-in-finder-classic/triggering-a-campaign-manually-with-finder-classic.md) のドキュメントを参照してください。

### リモートアクションのスクリプトからキャンペーンを実行する

キャンペーンと連携するには、リモートアクションスクリプトが .NET アセンブリ（`nxtcampaignaction.dll`）を読み込む必要があります。このアセンブリは Collector と共に従業員デバイスにインストールされます。 このアセンブリには **Nxt.CampaignAction** クラスが含まれており、キャンペーンの実行を制御し、従業員の回答を取得するためのメソッドを提供します。

アセンブリを読み込むには、スクリプトの先頭に次の行を追加します。

```
Add-Type -Path $env:NEXTHINK\RemoteActions\nxtcampaignaction.dll
```

キャンペーンを制御する **Nxt.CampaignAction** のメソッドは以下のとおりです。

```
[nxt.campaignaction]::RunCampaign(string campaignUid)
```

`campaignUid` で識別されるキャンペーンを実行し、従業員が回答を完了するまで待機します。 `campaignUid` の引数には UID または NQL ID（推奨）のいずれかを指定できます。 回答は `NxTrayResp` 型のオブジェクトとして返されます。

```
[nxt.campaignaction]::(string campaignUid, int timeout)
```

`campaignUid` で識別されるキャンペーンを実行し、従業員の回答完了、または `timeout`（秒）で指定された時間が経過するまで待機します。 `campaignUid` の引数には UID または NQL ID（推奨）のいずれかを指定できます。 回答は `NxTrayResp` 型のオブジェクトとして返されます。

```
[nxt.campaignaction]::RunStandAloneCampaign(string campaignUid)
```

`campaignUid` で識別されるキャンペーンを実行します。 `campaignUid` の引数には NQL ID（推奨）または UID のいずれかを指定できます。

```
string GetResponseStatus(NxTrayResp response)
```

`NxTrayResp` 型の応答オブジェクトが与えられた場合、このメソッドはキャンペーンのステータスを示す文字列を返します。 ステータスとして返される可能性のある値:

* **完全**: 従業員はキャンペーンの質問に完全に回答しました。
* **declined**：従業員はキャンペーンへの参加を辞退しました。
* **postponed**：従業員はキャンペーンへの参加に同意しました。
* **timeout**：従業員が回答を完了する前にキャンペーンがタイムアウトしました。
* **connectionfailed**：Collector コンポーネント間の通信エラーにより、スクリプトがキャンペーン通知を制御する Collector コンポーネントに接続できませんでした。
* **notificationfailed**：スクリプトが次のいずれかの理由によりキャンペーンを正常に表示できませんでした:
  * キャンペーンが存在しない、または未公開のため、プラットフォームからキャンペーン定義を取得できませんでした。
  * 別のキャンペーンが従業員にすでに表示されています。
  * フォーカス保護またはCollectorの「通知しない」ルールにより、非緊急のキャンペーンを表示できません。 詳細については、[キャンペーンの受信率を制限する](/platform/ja/user-guide/campaigns/managing-campaigns/creating-campaigns/limiting-the-reception-rate-of-campaigns.md)ドキュメントを参照してください。

```
string[] GetResponseAnswer(NxTrayResp response, string questionLabel)
```

`NxTrayResp` 型の応答オブジェクトと、キャンペーン内の質問を識別するラベルが与えられた場合、このメソッドは従業員の回答を返します。

* 単一回答質問の場合、返される文字列配列には 1 要素のみが含まれます。
* 複数回答質問の場合、返される文字列配列には従業員が選択した回答の数だけ要素が含まれます。 任意入力の自由記述テキストは無視されます。
* 従業員がキャンペーンに完全回答していない場合（例：ステータスが `fully` ではない場合）、返される文字列配列は空になります。 任意入力の自由記述テキストは無視されます。

{% hint style="danger" %}
セキュリティ上の理由から、セルフヘルプシナリオ用のリモートアクションでは、複数回答または意見尺度質問の任意入力の自由記述テキスト回答は無視されます。 セルフヘルプ専用に使用されるキャンペーンに任意入力の自由記述回答を含めることには意味がありません。
{% endhint %}

### スクリプトのエンコード

PowerShell スクリプトファイルは UTF-8（BOM 付き）でエンコードする必要があります。 BOM はファイル先頭に配置される必要がある Unicode 文字であり、UTF-8 では 16 進表記で `EF BB BF` の 3 バイト列として表されます。

Windows では、各コード行の末尾は `CR+LF` で終わる必要があります。

エラーやスクリプトの不具合を防ぐため、適切なエンコードを使用してください。

***

## コード例

<0>キャンペーンの呼び出し\</0>

この例では、リモートアクションが ID を使用して基本的なキャンペーン呼び出しを実行し、成功した場合はステータスメッセージを、失敗した場合はエラーメッセージを出力します。

```powershell
$result = [Nxt.CampaignAction]::RunCampaign($CampaignUid, $maxWaitTimeinSeconds)
$status = [Nxt.CampaignAction]::GetResponseStatus($result)
if ($status -eq "fully") {
        Write-Output "Campaign succeeded"
} else {
        Write-Output "Status is $status"
}
```

<details>

<summary>キャンペーンレスポンスへのアクセス</summary>

この例では、リモートアクションがキャンペーンの回答データを要求し、配列として出力します。 各回答は、それぞれ対応する番号付きオプションで表されます。 PowerShell ではインデックスが 0 から n-1 である点に注意してください。

```powershell
# Function to get campaign response
function Get-CampaignResponse ([string]$CampaignId) {
    return [nxt.campaignaction]::RunCampaign($CampaignId, $CAMPAIGN_TIMEOUT)
}
# Function to get campaign status
function Get-CampaignResponseStatus ($Response) {
    return [nxt.campaignaction]::GetResponseStatus($Response)
}

# Function to get response answers
function Get-CampaignResponseAnswer ($Response, [string]$QuestionName) {
    return [nxt.campaignaction]::GetResponseAnswer($Response, $QuestionName)[0]
}

#get campaign response
$campaignResponse = $null
$campaignResponse = Get-CampaignResponse -CampaignId $campaignId

# Get campaign status
$status = $null
$status = Get-CampaignResponseStatus -Response $campaignResponse
Write-Host "The response status is $status"

# Get response answers
$answersArray = $null
$answer = Get-CampaignResponseAnswer -Response $campaignResponse -QuestionName "Question1"
Write-Host "The answer is $answer"
```

</details>

<0>タイムアウト付きでキャンペーンを実行する\</0>

この例では、リモートアクションが、秒を入力として指定した時間後にタイムアウトし終了するキャンペーンを実行するよう設定されています。

```powershell
# Run a campaign with timeout
# timeout is in seconds (100s or 00:01:40)

$campaignId = "#my_campaign_nql_id"
$timeout = 100

function Get-CampaignResponse ([string]$CampaignId) {
    return [nxt.campaignaction]::RunCampaign($CampaignId, $timeout)
}

function Get-CampaignResponseStatus ($Response) {
    return [nxt.campaignaction]::GetResponseStatus($Response)
}

$result = Get-CampaignResponse -CampaignId $campaignId -timeout $timeout
$status = Get-CampaignResponseStatus -Response $result
if ($status -eq "fully") {
        Write-Output "Campaign succeeded"
} else {
        Write-Output "Status is $status"
}
```

<0>非ブロッキングキャンペーンの実行\</0>

この例では、リモートアクションはユーザー入力を必要とせず、キャンペーンをトリガーした後も実行を続けます。 ユーザーはいつでもキャンペーンを閉じることができます。 これは主に、データを取得するのではなく、ユーザーに情報を提供するために使用されます。

```powershell
##### ノンブロッキングキャンペーンの実行 #####

$mycampaignId = "#my_campaign_nql_id"
function Invoke-OperationCompletedCampaign ([string]$CampaignId) {
    [nxt.campaignaction]::RunStandAloneCampaign($CampaignId)
}

Invoke-OperationCompletedCampaign -CampaignId $mycampaignId
```

<details>

<summary>特定のキャンペーン回答に応じてアプリケーションを開く</summary>

この例では、リモートアクションスクリプトが Collector と共にデバイスにインストールされる .dll ファイルを読み込み、Collector とリモートアクション実行の仲介役として機能します。 これにより、PowerShell スクリプトから Collector へコマンドが送信され、`[Nxt.CampaignAction]` で始まる専用関数が利用可能になります。

リモートアクションは `Nxt.CampaignAction]::RunCampaign` 関数を使用し、キャンペーン ID とタイムアウト（秒）を入力としてキャンペーンを実行します。 次に、ユーザーの回答（または未回答）を収集し、そのデータを基にステータスを判断します。 ユーザーが `yes` と回答した場合、リモートアクションはプロセスを開始します。この例では Notepad が起動します。

```powershell
Add-Type -Path "$env:NEXTHINK\RemoteActions\nxtcampaignaction.dll"

$CampaignUid  = "<NQL ID of a single-answer campaign>"
$maxWaitTimeinSeconds = 60

$result = [Nxt.CampaignAction]::RunCampaign($CampaignUid, $maxWaitTimeinSeconds)
$status = [Nxt.CampaignAction]::GetResponseStatus($result)

if ($status -eq "fully") {
    $questionName = "question1"
    $choiceName =[Nxt.CampaignAction]::GetResponseAnswer($result, $questionName)
    if ($choiceName -eq "yes") {
        # user has confirmed - let's do some actions:
        Start-Process notepad.exe
    }
}
```

</details>

<details>

<summary>特定のアプリケーションがデバイスに存在するかの確認</summary>

この例では、リモートアクションが、入力として指定されたアプリケーション名がデバイス上に存在するかを Kanopy を使用して確認します。

```powershell
#
# Input parameters definition
#
param(
    [Parameter(Mandatory = $true)][string]$application_name
)
# End of parameters definition

$env:Path = 'C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\'

#
# Constants definition
#
$ERROR_EXCEPTION_TYPE = @{Environment = '[Environment error]'
    Input = '[Input error]'
    Internal = '[Internal error]'
}
Set-Variable -Name 'ERROR_EXCEPTION_TYPE' -Option ReadOnly -Scope Script -Force

$LOCAL_SYSTEM_IDENTITY = 'S-1-5-18'
Set-Variable -Name 'LOCAL_SYSTEM_IDENTITY' -Option ReadOnly -Scope Script -Force

$REMOTE_ACTION_DLL_PATH = "$env:NEXTHINK\RemoteActions\nxtremoteactions.dll"
Set-Variable -Name 'REMOTE_ACTION_DLL_PATH' -Option ReadOnly -Scope Script -Force

$WINDOWS_VERSIONS = @{Windows7 = '6.1'
    Windows8 = '6.2'
    Windows81 = '6.3'
    Windows10 = '10.0'
    Windows11 = '10.0'
}
Set-Variable -Name 'WINDOWS_VERSIONS' -Option ReadOnly -Scope Script -Force



#
# Invoke Main
#
function Invoke-Main ([hashtable]$InputParameters) {
    $exitCode = 0
    $appPresent = $false
    try {
        Add-NexthinkRemoteActionDLL
        Test-RunningAsLocalSystem
        Test-MinimumSupportedOSVersion -WindowsVersion 'Windows8'
        Test-InputParameter -InputParameters $InputParameters

        $appPresent = Invoke-CheckApplcationExistance -appName $InputParameters.application_name
    } catch {
        Write-StatusMessage -Message $_
        $exitCode = 1
    } finally {
        Update-EngineOutputVariables -applicationPresent $appPresent
    }

    return $exitCode
}

#
# Template functions
#
function Add-NexthinkRemoteActionDLL {

    if (-not (Test-Path -Path $REMOTE_ACTION_DLL_PATH)) {
        throw "$($ERROR_EXCEPTION_TYPE.Environment) Nexthink Remote Action DLL not found. "
    }
    Add-Type -Path $REMOTE_ACTION_DLL_PATH
}

function Test-RunningAsLocalSystem {

    if (-not (Confirm-CurrentUserIsLocalSystem)) {
        throw "$($ERROR_EXCEPTION_TYPE.Environment) This script must be run as LocalSystem. "
    }
}

function Confirm-CurrentUserIsLocalSystem {

    $currentIdentity = Get-CurrentIdentity
    return $currentIdentity -eq $LOCAL_SYSTEM_IDENTITY
}

function Get-CurrentIdentity {

    return [security.principal.windowsidentity]::GetCurrent().User.ToString()
}

function Test-MinimumSupportedOSVersion ([string]$WindowsVersion, [switch]$SupportedWindowsServer) {
    $currentOSInfo = Get-OSVersionType
    $OSVersion = $currentOSInfo.Version -as [version]

    $supportedWindows = $WINDOWS_VERSIONS.$WindowsVersion -as [version]

    if (-not ($currentOSInfo)) {
        throw "$($ERROR_EXCEPTION_TYPE.Environment) This script could not return OS version. "
    }

    if ( $SupportedWindowsServer -eq $false -and $currentOSInfo.ProductType -ne 1) {
        throw "$($ERROR_EXCEPTION_TYPE.Environment) This script is not compatible with Windows Servers. "
    }

    if ( $OSVersion -lt $supportedWindows) {
        throw "$($ERROR_EXCEPTION_TYPE.Environment) This script is compatible with $WindowsVersion and later only. "
    }
}

function Get-OSVersionType {

    return Get-WindowsManagementData -Class Win32_OperatingSystem | Select-Object -Property Version,ProductType
}

function Get-WindowsManagementData ([string]$Class, [string]$Namespace = 'root/cimv2') {
    try {
        $query = [wmisearcher] "Select * from $Class"
        $query.Scope.Path = "$Namespace"
        $query.Get()
    } catch {
        throw "$($ERROR_EXCEPTION_TYPE.Environment) Error getting CIM/WMI information. Verify WinMgmt service status and WMI repository consistency. "
    }
}

function Write-StatusMessage ([psobject]$Message) {
    $exceptionMessage = $Message.ToString()

    if ($Message.InvocationInfo.ScriptLineNumber) {
        $version = Get-ScriptVersion
        if (-not [string]::IsNullOrEmpty($version)) {
            $scriptVersion = "Version: $version. "
        }

        $errorMessageLine = $scriptVersion + "Line '$($Message.InvocationInfo.ScriptLineNumber)': "
    }

    $host.ui.WriteErrorLine($errorMessageLine + $exceptionMessage)
}

function Get-ScriptVersion {

    $scriptContent = Get-Content $MyInvocation.ScriptName | Out-String
    if ($scriptContent -notmatch '<#[\r\n]{2}.SYNOPSIS[^\#\>]*(.NOTES[^\#\>]*)\#>') { return }

    $helpBlock = $Matches[1].Split([environment]::NewLine)

    foreach ($line in $helpBlock) {
        if ($line -match 'Version:') {
            return $line.Split(':')[1].Split('-')[0].Trim()
        }
    }
}

function Test-StringNullOrEmpty ([string]$ParamName, [string]$ParamValue) {
    if ([string]::IsNullOrEmpty((Format-StringValue -Value $ParamValue))) {
        throw "$($ERROR_EXCEPTION_TYPE.Input) '$ParamName' cannot be empty nor null. "
    }
}

function Format-StringValue ([string]$Value) {
    return $Value.Replace('"', '').Replace("'", '').Trim()
}

#
# Input parameter validation
#
function Test-InputParameter ([hashtable]$InputParameters) {
    Test-StringNullOrEmpty `
        -ParamName 'application_name' `
        -ParamValue $InputParameters.application_name
}

#
# application management
#
function Invoke-CheckApplcationExistance ([string]$appName) {

    $installedApps = Get-CimInstance -Query "SELECT * FROM Win32_Product WHERE Name LIKE '%$appName%'"

    if ($installedApps) {
        return $true
    } else {
        return $false
    }
}

#
# Nexthink Output management
#
function Update-EngineOutputVariables ([bool]$applicationPresent) {

        [nxt]::WriteOutputBool('application_present', $applicationPresent)
}

#
# Main script flow
#
[environment]::Exit((Invoke-Main -InputParameters $MyInvocation.BoundParameters))
```

</details>

<details>

<summary>デバイス上に特定のアプリケーションが存在するかどうかの確認：エラー処理</summary>

この例では、Remote action がアプリケーションログのパスとエラーコードを入力として使用し、指定されたエラーコードをログから解析します。 このコードが存在する場合、エラーメッセージを出力します。

```powershell
#
# 入力パラメーターの定義
#
param(
    [Parameter(Mandatory = $true)][string]$application_log_path,
    [Parameter(Mandatory = $true)][string]$error_code
)
# パラメーター定義の終了

$env:Path = 'C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\'

#
# 定数の定義
#
$ERROR_EXCEPTION_TYPE = @{Environment = '[環境エラー]'
    Input = '[入力エラー]'
    Internal = '[内部エラー]'
}
Set-Variable -Name 'ERROR_EXCEPTION_TYPE' -Option ReadOnly -Scope Script -Force

$LOCAL_SYSTEM_IDENTITY = 'S-1-5-18'
Set-Variable -Name 'LOCAL_SYSTEM_IDENTITY' -Option ReadOnly -Scope Script -Force

$REMOTE_ACTION_DLL_PATH = "$env:NEXTHINK\RemoteActions\nxtremoteactions.dll"
Set-Variable -Name 'REMOTE_ACTION_DLL_PATH' -Option ReadOnly -Scope Script -Force

$WINDOWS_VERSIONS = @{Windows7 = '6.1'
    Windows8 = '6.2'
    Windows81 = '6.3'
    Windows10 = '10.0'
    Windows11 = '10.0'
}
Set-Variable -Name 'WINDOWS_VERSIONS' -Option ReadOnly -Scope Script -Force



#
# メインを呼び出す
#
function Invoke-Main ([hashtable]$InputParameters) {
    $exitCode = 0
    $outputs = @{
        'error_message' = "-"
        'error_found' = $false
    }
    try {
        Add-NexthinkRemoteActionDLL
        Test-RunningAsLocalSystem
        Test-MinimumSupportedOSVersion -WindowsVersion 'Windows8'
        Test-InputParameter -InputParameters $InputParameters

        $outputs = Invoke-CheckApplicationLogError -applicationLogPath $InputParameters.application_log_path -errorCode $InputParameters.error_code
    } catch {
        Write-StatusMessage -Message $_
        $exitCode = 1
    } finally {
        Update-EngineOutputVariables -OutputData $outputs
    }

    return $exitCode
}

#
# テンプレート関数
#
function Add-NexthinkRemoteActionDLL {

    if (-not (Test-Path -Path $REMOTE_ACTION_DLL_PATH)) {
        throw "$($ERROR_EXCEPTION_TYPE.Environment) Nexthink Remote Action DLLが見つかりません。 "
    }
    Add-Type -Path $REMOTE_ACTION_DLL_PATH
}

function Test-RunningAsLocalSystem {

    if (-not (Confirm-CurrentUserIsLocalSystem)) {
        throw "$($ERROR_EXCEPTION_TYPE.Environment) このスクリプトはLocalSystemとして実行する必要があります。 "
    }
}

function Confirm-CurrentUserIsLocalSystem {

    $currentIdentity = Get-CurrentIdentity
    return $currentIdentity -eq $LOCAL_SYSTEM_IDENTITY
}

function Get-CurrentIdentity {

    return [security.principal.windowsidentity]::GetCurrent().User.ToString()
}

function Test-MinimumSupportedOSVersion ([string]$WindowsVersion, [switch]$SupportedWindowsServer) {
    $currentOSInfo = Get-OSVersionType
    $OSVersion = $currentOSInfo.Version -as [version]

    $supportedWindows = $WINDOWS_VERSIONS.$WindowsVersion -as [version]

    if (-not ($currentOSInfo)) {
        throw "$($ERROR_EXCEPTION_TYPE.Environment) このスクリプトではOSバージョンを取得できませんでした。 "
    }

    if ( $SupportedWindowsServer -eq $false -and $currentOSInfo.ProductType -ne 1) {
        throw "$($ERROR_EXCEPTION_TYPE.Environment) このスクリプトはWindows Serverと互換性がありません。 "
    }

    if ( $OSVersion -lt $supportedWindows) {
        throw "$($ERROR_EXCEPTION_TYPE.Environment) このスクリプトは$WindowsVersion以降とのみ互換性があります。 "
    }
}

function Get-OSVersionType {

    return Get-WindowsManagementData -Class Win32_OperatingSystem | Select-Object -Property Version,ProductType
}

function Get-WindowsManagementData ([string]$Class, [string]$Namespace = 'root/cimv2') {
    try {
        $query = [wmisearcher] "Select * from $Class"
        $query.Scope.Path = "$Namespace"
        $query.Get()
    } catch {
        throw "$($ERROR_EXCEPTION_TYPE.Environment) CIM/WMI情報の取得中にエラーが発生しました。WinMgmtサービスの状態とWMIリポジトリの整合性を確認してください。 "
    }
}

function Write-StatusMessage ([psobject]$Message) {
    $exceptionMessage = $Message.ToString()

    if ($Message.InvocationInfo.ScriptLineNumber) {
        $version = Get-ScriptVersion
        if (-not [string]::IsNullOrEmpty($version)) {
            $scriptVersion = "バージョン: $version。 "
        }

        $errorMessageLine = $scriptVersion + "行 '$($Message.InvocationInfo.ScriptLineNumber)': "
    }

    $host.ui.WriteErrorLine($errorMessageLine + $exceptionMessage)
}

function Get-ScriptVersion {

    $scriptContent = Get-Content $MyInvocation.ScriptName | Out-String
    if ($scriptContent -notmatch '<#[\r\n]{2}.SYNOPSIS[^\#\>]*(.NOTES[^\#\>]*)\#>') { return }

    $helpBlock = $Matches[1].Split([environment]::NewLine)

    foreach ($line in $helpBlock) {
        if ($line -match 'Version:') {
            return $line.Split(':')[1].Split('-')[0].Trim()
        }
    }
}

function Test-StringNullOrEmpty ([string]$ParamName, [string]$ParamValue) {
    if ([string]::IsNullOrEmpty((Format-StringValue -Value $ParamValue))) {
        throw "$($ERROR_EXCEPTION_TYPE.Input) '$ParamName'を空またはnullにすることはできません。 "
    }
}

function Format-StringValue ([string]$Value) {
    return $Value.Replace('"', '').Replace("'", '').Trim()
}

function Test-ParamIsInteger ([string]$ParamName, [string]$ParamValue) {
    $intValue = $ParamValue -as [int]
    if ([string]::IsNullOrEmpty($ParamValue) -or $null -eq $intValue) {
        throw "$($ERROR_EXCEPTION_TYPE.Input) パラメーター '$ParamName' でエラーが発生しました。'$ParamValue' は整数ではありません。 "
    }
}

#
# 入力パラメーターの検証
#
function Test-InputParameter ([hashtable]$InputParameters) {
    Test-StringNullOrEmpty `
        -ParamName 'application_log_path' `
        -ParamValue $InputParameters.application_log_path 
    Test-ValidPath `
        -ParamName 'application_log_path' `
        -ParamValue $InputParameters.application_log_path
    Test-ParamIsInteger `
        -ParamName 'error_code' `
        -ParamValue $InputParameters.error_code
}

function Test-ValidPath ([string]$ParamName, [string]$ParamValue) {
    if (-not (Test-Path -Path $ParamValue)) {
        throw "$ParamName は有効なパスではないか、アクセスできません。"
    }
}

#
# アプリケーション管理
#
function Invoke-CheckApplicationLogError ([string]$applicationLogPath, [string]$errorCode) {

    $returnValues = @{
        'error_message' = "-"
        'error_found' = $false
    }

    $errorMatches = Select-String -Path $applicationLogPath -Pattern $errorCode | Select-Object -First 1

    if ($errorMatches) {
        $errorLine =  $errorMatches.Line
        $errorMessage = $errorLine.Split(":")[1].Trim()
        $returnValues.error_message = $errorMessage
        $returnValues.error_found = $true
        
    } else {
        $returnValues.error_found = $false
    }

    return $returnValues
}

#
# Nexthink出力管理
#
function Update-EngineOutputVariables ([hashtable]$outputData) {

        [nxt]::WriteOutputString('error_message', $outputData.error_message )
        [nxt]::WriteOutputBool('error_found', $outputData.error_found )
}

#
# メインスクリプトフロー
#
[environment]::Exit((Invoke-Main -InputParameters $MyInvocation.BoundParameters))
```

</details>

<details>

<summary>デバイスに特定のアプリケーションが存在するかを確認する: エラー修復</summary>

この例では、リモートアクションはキャンペーンを使用して、アプリケーションがすでに実行中の場合は再起動が必要であることをユーザーに通知し、実行中でない場合は起動します：

```powershell
#
# 入力パラメーターの定義
#
param(
    [Parameter(Mandatory = $true)][string]$initial_camapign_id,
    [Parameter(Mandatory = $true)][string]$final_campaign_id,
    [Parameter(Mandatory = $true)][string]$inform_failure_campaign_id
)
# パラメーター定義の終了

$env:Path = 'C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\'

#
# 定数の定義
#
$CAMPAIGN_DLL_PATH = "$env:NEXTHINK\RemoteActions\nxtcampaignaction.dll"
Set-Variable -Name 'CAMPAIGN_DLL_PATH' -Option ReadOnly -Scope Script -Force

$ERROR_EXCEPTION_TYPE = @{Environment = '[環境エラー]'
    Input = '[入力エラー]'
    Internal = '[内部エラー]'
}
Set-Variable -Name 'ERROR_EXCEPTION_TYPE' -Option ReadOnly -Scope Script -Force

$LOCAL_SYSTEM_IDENTITY = 'S-1-5-18'
Set-Variable -Name 'LOCAL_SYSTEM_IDENTITY' -Option ReadOnly -Scope Script -Force

$NQL_ID_FORMAT_REGEX = "^[#]*([a-zA-Z0-9_]+_)*[a-zA-Z0-9_#]*$"
Set-Variable -Name 'NQL_ID_FORMAT_REGEX' -Option ReadOnly -Scope Script -Force

$REMOTE_ACTION_DLL_PATH = "$env:NEXTHINK\RemoteActions\nxtremoteactions.dll"
Set-Variable -Name 'REMOTE_ACTION_DLL_PATH' -Option ReadOnly -Scope Script -Force

$WINDOWS_VERSIONS = @{Windows7 = '6.1'
    Windows8 = '6.2'
    Windows81 = '6.3'
    Windows10 = '10.0'
    Windows11 = '10.0'
}
Set-Variable -Name 'WINDOWS_VERSIONS' -Option ReadOnly -Scope Script -Force

$KANOPY_APPLICATION_PROCESS_NAME = 'kanopyagent'
Set-Variable -Name 'KANOPY_SERVICE_NAME' -Option ReadOnly -Scope Script -Force

$KANOPY_APPLICATION_EXECUTABLE_PATH = 'C:\ProgramData\Kanopy\KanopyAgent\KanopyAgent.exe'
Set-Variable -Name 'KANOPY_APPLICATION_EXECUTABLE_PATH' -Option ReadOnly -Scope Script -Force

#
# メインを呼び出す
#
function Invoke-Main ([hashtable]$InputParameters) {
    $exitCode = 0

    try {
        Add-NexthinkDLLs
        Test-RunningAsInteractiveUser
        Test-MinimumSupportedOSVersion -WindowsVersion 'Windows8'
        Test-InputParameter -InputParameters $InputParameters

        $outputs = Invoke-RemediationAction -InputParameters $InputParameters
    } catch {
        Write-StatusMessage -Message $_
        $exitCode = 1
    }

    return $exitCode
}

#
# テンプレート関数
#
function Add-NexthinkDLLs {

    if (-not (Test-Path -Path $REMOTE_ACTION_DLL_PATH)) {
        throw "$($ERROR_EXCEPTION_TYPE.Environment) Nexthink Remote Action DLLが見つかりません。 "
    }
    if (-not (Test-Path -Path $CAMPAIGN_DLL_PATH)) {
        throw "$($ERROR_EXCEPTION_TYPE.Environment) Nexthink Campaign DLLが見つかりません。 "
    }
    Add-Type -Path $REMOTE_ACTION_DLL_PATH
    Add-Type -Path $CAMPAIGN_DLL_PATH
}

function Test-RunningAsInteractiveUser {

    if (Confirm-CurrentUserIsLocalSystem) {
        throw "$($ERROR_EXCEPTION_TYPE.Environment) このスクリプトはInteractiveUserとして実行する必要があります。 "
    }
}

function Confirm-CurrentUserIsLocalSystem {

    $currentIdentity = Get-CurrentIdentity
    return $currentIdentity -eq $LOCAL_SYSTEM_IDENTITY
}

function Get-CurrentIdentity {

    return [security.principal.windowsidentity]::GetCurrent().User.ToString()
}

function Test-MinimumSupportedOSVersion ([string]$WindowsVersion, [switch]$SupportedWindowsServer) {
    $currentOSInfo = Get-OSVersionType
    $OSVersion = $currentOSInfo.Version -as [version]

    $supportedWindows = $WINDOWS_VERSIONS.$WindowsVersion -as [version]

    if (-not ($currentOSInfo)) {
        throw "$($ERROR_EXCEPTION_TYPE.Environment) このスクリプトではOSバージョンを取得できませんでした。 "
    }

    if ( $SupportedWindowsServer -eq $false -and $currentOSInfo.ProductType -ne 1) {
        throw "$($ERROR_EXCEPTION_TYPE.Environment) このスクリプトはWindows Serverと互換性がありません。 "
    }

    if ( $OSVersion -lt $supportedWindows) {
        throw "$($ERROR_EXCEPTION_TYPE.Environment) このスクリプトは$WindowsVersion以降とのみ互換性があります。 "
    }
}

function Get-OSVersionType {

    return Get-WindowsManagementData -Class Win32_OperatingSystem | Select-Object -Property Version,ProductType
}

function Get-WindowsManagementData ([string]$Class, [string]$Namespace = 'root/cimv2') {
    try {
        $query = [wmisearcher] "Select * from $Class"
        $query.Scope.Path = "$Namespace"
        $query.Get()
    } catch {
        throw "$($ERROR_EXCEPTION_TYPE.Environment) CIM/WMI情報の取得中にエラーが発生しました。WinMgmtサービスの状態とWMIリポジトリの整合性を確認してください。 "
    }
}

function Write-StatusMessage ([psobject]$Message) {
    $exceptionMessage = $Message.ToString()

    if ($Message.InvocationInfo.ScriptLineNumber) {
        $version = Get-ScriptVersion
        if (-not [string]::IsNullOrEmpty($version)) {
            $scriptVersion = "バージョン: $version。 "
        }

        $errorMessageLine = $scriptVersion + "行 '$($Message.InvocationInfo.ScriptLineNumber)': "
    }

    $host.ui.WriteErrorLine($errorMessageLine + $exceptionMessage)
}

function Get-ScriptVersion {

    $scriptContent = Get-Content $MyInvocation.ScriptName | Out-String
    if ($scriptContent -notmatch '<#[\r\n]{2}.SYNOPSIS[^\#\>]*(.NOTES[^\#\>]*)\#>') { return }

    $helpBlock = $Matches[1].Split([environment]::NewLine)

    foreach ($line in $helpBlock) {
        if ($line -match 'Version:') {
            return $line.Split(':')[1].Split('-')[0].Trim()
        }
    }
}

function Test-CampaignID ([string]$ParamName, [string]$ParamValue) {
    if ([string]::IsNullOrEmpty($ParamValue)) {
        throw "$($ERROR_EXCEPTION_TYPE.Input) パラメーター '$ParamName' でエラーが発生しました。値をnullまたは空にすることはできません。 "
    }

    if (-not ($ParamValue -as [guid]) -and ($ParamValue -notmatch $NQL_ID_FORMAT_REGEX)) {
        throw "$($ERROR_EXCEPTION_TYPE.Input) パラメーター '$ParamName' でエラーが発生しました。UIDまたはNQL ID値のみを使用できます。 "
    }
}

function Write-NxtLog ([string]$Message, [object]$Object) {
    if (Test-PowerShellVersion -MinimumVersion 5) {
        $currentDate = Get-Date -Format 'yyyy/MM/dd hh:mm:ss'
        if ($Object) {
            $jsonObject = $Object | ConvertTo-Json -Compress -Depth 100
            Write-Information -MessageData "$currentDate - $Message $jsonObject"
        } else {
            Write-Information -MessageData "$currentDate - $Message"
        }
    }
}

function Test-PowerShellVersion ([int]$MinimumVersion) {
    if ((Get-Host).Version.Major -ge $MinimumVersion) {
        return $true
    }
}

function Get-CampaignResponseTimeout ([string]$CampaignId, [int]$CampaignTimeout) {
    return [nxt.campaignaction]::RunCampaign($CampaignId,$CampaignTimeout)
}

function Get-CampaignResponseStatus ($Response) {
    return [nxt.campaignaction]::GetResponseStatus($Response)
}

function Get-CampaignResponseAnswer ($Response, [string]$QuestionName) {
    return [nxt.campaignaction]::GetResponseAnswer($Response, $QuestionName)[0]
}

function Invoke-OperationCompletedCampaign ([string]$CampaignId) {
    [nxt.campaignaction]::RunStandAloneCampaign($CampaignId)
}

#
# 入力パラメーターの検証
#
function Test-InputParameter ([hashtable]$InputParameters) {
    Test-CampaignID `
        -ParamName 'initial_camapign_id' `
        -ParamValue $InputParameters.initial_camapign_id 
    Test-CampaignID `
        -ParamName 'final_campaign_id' `
        -ParamValue $InputParameters.final_campaign_id
    Test-CampaignID `
        -ParamName 'inform_failure_campaign_id' `
        -ParamValue $InputParameters.inform_failure_campaign_id
}

#
# キャンペーン応答管理
#

function Invoke-Campaign ([string]$CampaignId) {
    Write-NxtLog -Message "$($MyInvocation.MyCommand) を呼び出しています"

    $response = Get-CampaignResponseTimeout -CampaignId $CampaignId -CampaignTimeout 60
    $status = Get-CampaignResponseStatus -Response $response
    switch ($status) {
        'fully' {
            $answer = Get-CampaignResponseAnswer -Response $response -QuestionName 'question_label'
            if ($answer -eq 'yes_label') {
                return $true
            } elseif ($answer -eq 'no_label') {
                return $false
            } else {
                throw "ユーザーから予期しない回答を受信しました: $answer。 "
            }
        }
        'timeout' { throw "ユーザーからの回答取得がタイムアウトしました。 " }
        'declined' {throw "ユーザーがキャンペーンを拒否しました。 " }
        'postponed' {throw "ユーザーがキャンペーンを延期しました。 " }
        'connectionfailed' { throw "キャンペーン通知を制御するCollectorコンポーネントに接続できません。 " }
        'notificationfailed' { throw "キャンペーン通知を制御するCollectorコンポーネントに通知できません。 " }
            default { throw "キャンペーン応答を処理できませんでした: $response。 " }
        }
}

#
# アプリケーション管理
#
function Invoke-RemediationAction ([hashtable]$InputParameters) {

    $process = Get-Process -Name $KANOPY_APPLICATION_PROCESS_NAME -ErrorAction SilentlyContinue
    if ($process) {
        $userResponse = Invoke-Campaign -CampaignId $InputParameters.initial_camapign_id

        if ($userResponse -eq $true) {

            try {
                Stop-Process -Name $KANOPY_APPLICATION_PROCESS_NAME -Force -ErrorAction Stop | Out-Null
                Write-StatusMessage -Message "Kanopyプロセスは正常に停止しました。"
            } catch {
                Invoke-OperationCompletedCampaign -CampaignId $InputParameters.inform_failure_campaign_id
                throw "Kanopyプロセスを停止できませんでした。エラー: $_"
            }

            Start-Sleep -Seconds 2

            try {
                Start-Process -Name $KANOPY_APPLICATION_PROCESS_NAME -Force -ErrorAction Stop | Out-Null
                Write-StatusMessage -Message "Kanopyプロセスは正常に開始しました。"
            } catch {
                Invoke-OperationCompletedCampaign -CampaignId $InputParameters.inform_failure_campaign_id
                throw "停止後にKanopyプロセスを開始できませんでした。エラー: $_"
            }

            Invoke-OperationCompletedCampaign -CampaignId $InputParameters.final_campaign_id
        } else {
            Write-StatusMessage -Message "ユーザーは修復アクションを拒否しました。"
        }
    } else {
        Write-StatusMessage -Message "Kanopyプロセスは実行されていません。アプリケーションを開始します。"

        try {
            Start-Process -FilePath $KANOPY_APPLICATION_EXECUTABLE_PATH -ErrorAction Stop
        } catch {
            throw "Kanopyプロセスを開始できませんでした。エラー: $_"
        }
        Start-Sleep -Seconds 2

        $processCheck = Get-Process -Name $KANOPY_APPLICATION_PROCESS_NAME -ErrorAction SilentlyContinue
        if ($processCheck) {
            Write-StatusMessage -Message "Kanopyプロセスは正常に開始しました。"
        } else {
            throw "Kanopyプロセスが正常に開始されませんでした。"
        }
    }

}

#
# メインスクリプトフロー
#
[environment]::Exit((Invoke-Main -InputParameters $MyInvocation.BoundParameters))
```

</details>

***

## スクリプトの署名

{% hint style="info" %}
Nexthinkでは、本番環境ですべてのスクリプトに署名することを推奨しています。 署名されていないスクリプトは、テスト環境でのみ使用してください。
{% endhint %}

### 証明書の取得

PowerShell スクリプトに署名するには、以下のように [Set-Authenticode](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-authenticodesignature?view=powershell-7) コマンドを使用します：

1. コード署名証明書は以下から取得します：
   * [PowerShell 証明書プロバイダー](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.security/about/about_certificate_provider?view=powershell-7):\
     `$cert = Get-ChildItem -Path Cert:\CurrentUser\My -CodeSigningCert`
   * PFXファイル:\
     `$cert = Get-PfxCertificate -FilePath C:\Test\Mysign.pfx`
2. 証明書を使用して、たとえば`remoteaction.ps1`などのリモートアクション用スクリプトに署名します。 証明書の有効期限後も機能し続けるように、タイムスタンプを追加します。 以下の例では、DigiCertタイムスタンプサーバーを使用しています。\
   `Set-AuthenticodeSignature -FilePath .\remoteaction.ps1 -Certificate $cert -IncludeChain All -TimestampServer "http://timestamp.digicert.com"`
3. （任意）スクリプト内の署名を検証します。\
   `Get-AuthenticodeSignature .\remoteaction.ps1 -Verbose | fl`

{% hint style="danger" %}
プライベート認証局（CA）を使用する場合は、キャッシュによるサーバーの過負荷を防ぐため、OCSPのベストプラクティスを採用していることを確認してください。
{% endhint %}

### エンドポイントへの証明書の展開

デフォルトポリシー（`signed_trusted_or_nexthink`）では、Nexthink Libraryの公式リモートアクションを、追加設定なしでデバイス上で実行できます。

{% hint style="info" %}
リモートアクション用に独自のスクリプトを作成して署名する場合は、Microsoft Windowsの**ローカル コンピューター** **> 信頼された発行元**証明書ストアに署名証明書を追加してください。
{% endhint %}

厳格な`signed_trusted`ポリシーを使用する場合は、ライブラリおよびシステムスクリプトに独自の証明書で再署名するか、Nexthinkコード署名証明書をMicrosoft Windowsの**ローカル コンピューター** **> 信頼された発行元**証明書ストアに展開できます。

{% hint style="danger" %}
Microsoft Windowsの**ローカル コンピューター** **>** **信頼された発行元**ストアにコード署名証明書を追加しない場合、システムで次のエラーが生成されます。**リモートアクションを実行できませんでした: スクリプト署名が無効であるか、証明書が信頼されていません。**
{% endhint %}

証明書が、Windowsのローカル コンピューターの信頼されたルート証明機関証明書ストアにルート証明書がまだ存在しないプライベートCAによって生成された場合は、ルート証明書が追加されていることを確認してください。

スクリプトの署名に中間証明書を使用した場合は、ローカルコンピューターの「中間証明機関」証明書ストアに中間証明書の完全なチェーンを含めてください:

1. Microsoft Windows に管理者としてログインします。
2. **Win+R** キーを押して［ファイル名を指定して実行］ダイアログを開きます:
   1. **certlm.msc** と入力します。
   2. **OK** をクリックします。
3. プログラムにデバイスへの変更を許可するために **はい** をクリックします。
4. 左側の一覧で、目的の証明書ストア（例：**信頼された発行元**）の名前を右クリックします。
   1. コンテキストメニューから **すべてのタスク > インポート...** を選択して証明書のインポートウィザードを開始します。
5. ウィザードを開始するために **次へ** をクリックします。
6. **参照** をクリックして証明書ファイルを選択します。
7. **次へ** をクリックします。
8. ［次のストアに証明書をすべて配置する］ダイアログで提案された証明書ストアを受け入れるために **次へ** をクリックします。
9. インポートする証明書を確認し、**完了** をクリックします。

<figure><img src="/files/XdExa00G8ddfMWKJgPuA" alt="Certificates" width="546"><figcaption></figcaption></figure>

{% hint style="info" %}
Nexthink は、グループポリシーオブジェクト（GPO）や Microsoft Intune ポリシーなどの管理ツールを使用して、すべてのデバイスに同時に証明書を展開することを推奨しています。
{% endhint %}

## スクリプトの保守

### 比較と検証

リモートアクションスクリプトを展開する前に、Nexthink が作成した他のスクリプトと比較することができます。 この手順は任意ですが、初めてスクリプトを準備する場合は推奨されます。

1. Nexthink Library で **Content** を選択します。
2. **Remote action** でフィルタリングします。
3. **Remote Actions** の管理ページに移動します。
4. ターゲットのオペレーティングシステムに一致する、Nexthink Library から直接インストールされた任意のリモートアクションスクリプトを選択します。
5. スクリプトをエクスポートし、自身のスクリプトと構文を比較します。

### エラー処理

Nexthink は、スクリプトを実行した PowerShell プロセスの戻り値に基づいてリモートアクションの実行が成功したかどうかを判定します:

* 終了コードが 0 の場合、実行は成功です。
* 0 以外の値はエラーを示します。

PowerShell の未処理例外により、スクリプトが適切な終了コードを返さずに終了する場合があります。 予期しないエラーに対処するため、Nexthink はすべてのスクリプトの本文を次のコードスニペットで開始することを推奨しています:

```powershell
 trap {
     $host.ui.WriteErrorLine($_.ToString())
     exit 1
 }
```

このデフォルトのエラーハンドラーは、必要な DLL 依存関係の読み込みの後、任意の形式パラメーター宣言の下に配置してください。

### パフォーマンス測定

スクリプトのパフォーマンスとリソース使用量を測定するには、[Collector configuration](/platform/ja/configuring_nexthink/bringing-data-into-your-nexthink-instance/deploying-nexthink-in-non-vdi-environment/installing-collector/windows-collector-references/collector-configuration-tool-for-windows.md) ツールを使用して、Collector のログをデバッグモードで有効にします:

```
nxtcfg.exe /s logmode=2
```

出力は `nxtcod.log` ファイルに保存されます。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.nexthink.com/platform/ja/user-guide/remote-actions/setting-up-and-managing-remote-actions/creating-remote-actions/writing-scripts-for-remote-actions-on-windows.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
