この記事では、Windows上でNexthinkリモートアクションスクリプトを準備するプロセスの詳細について説明します。 スクリプトは、Windows .NET Framework上に構築されたMicrosoftのスクリプト言語であるPowerShellで作成され、その後、安全性を確保するために証明書で署名されます。 PowerShellスクリプトは、タスクの自動化や構成管理に適しており、従業員のデバイスでリモート操作を実行することができます。
リモートアクションの主な使用事例は、デバイスからのオンデマンドデータ収集、自己修復タスク、構成設定の変更です。
この記事は、読者がPowerShellスクリプト作成に精通していることを前提としています。
スクリプトの作成
汎用スクリプトと入力変数
汎用スクリプトは、署名済みスクリプトにカスタマイズが必要な状況で役立ちます。 署名済みスクリプトを変更すると署名が無効になりますが、汎用スクリプトはパラメーターを通してカスタマイズでき、署名を保持します。
PowerShellスクリプトの冒頭で形式的なパラメーターを宣言し、それを汎用化します。 関連するリモートアクションを編集するとき、パラメーターの値はNexthinkのwebインターフェース内で変更できます。
たとえば、汎用的なレジストリキーから読み込むスクリプトを作成するには、レジストリ内のキーへのパスを含むパラメーターをスクリプトで宣言します。 複数のリモートアクションがスクリプト内のパラメーターに異なるパスを指定することにより、異なるレジストリキーからデータを読み取るために同じスクリプトを使用できます。
param(
[string]$filePath,
[string]$regPath
)
リモートアクションの構成中にスクリプトをアップロードすると、システムは任意のインポートされたPowerShellスクリプトのパラメーターを認識し、パラメーターセクションに一覧表示します。 各パラメーター名の右側に表示されるテキスト入力ボックスに実際の値を入力します。
出力変数の作成
スクリプトを実行すると、オンデマンドデータとして保存したい出力が生成されることがあります。 Nexthinkは.NETアセンブリ(nxtremoteactions.dll
)を提供しており、これはCollectorと同時に社員のデバイスにインストールされます。 このアセンブリには、結果をデータ層に書き込むためのメソッドを提供するNxtというクラスが含まれています。
Nxtクラスを使うには、リモートアクションのためのPowerShellスクリプトの冒頭に次の行を追加します:
Add-Type -Path $env:NEXTHINK\RemoteActions\nxtremoteactions.dll
Nxtクラスのメソッドを使って、望む出力を書き込みます。 すべての書き込みメソッドは2つの引数を受け入れます:出力の名前と書き込む値です。 例えば、ファイルサイズをデータ層に書き込むには:
[Nxt]::WriteOutputSize("FileSize", $fileSize)
リモートアクション設定中にスクリプトをアップロードすると、システムはスクリプト内での出力書き込みの呼び出しを認識し、スクリプトテキストの下の** Outputs **セクションに出力変数を一覧表示します。 調査やメトリックで参照する際の方法を示すように出力のラベルを設定します。
書き込まれた各メソッドの終了は出力の種類を示します。 使用可能なメソッドと書き込む値の対応するPowerShellのタイプを以下の表で見つけてください:
Nxt 書き込みメソッド
PowerShell タイプ
制約条件
0 - 1024文字(出力が大きい場合<し>切り捨て)
キャンペーンの実施
リモートアクションをキャンペーンと組み合わせて、社員が問題を自主的に解決できるようにします。 キャンペーンにより、問題の検出について社員に知らせ、その解決を案内できます。
デバイスを操作中の社員のデスクトップにキャンペーンを表示させるには:
キャンペーンはリモートアクショントリガーを持ち、公開されている必要があります。
リモートアクションのスクリプトは次のいずれかで実行できます:
アクションが特別な権限を必要としない場合、社員のコンテキストで。
アクションが管理特権を必要とする場合、ローカルシステムアカウントのコンテキストで。
キャンペーン識別子の取得
リモートアクションからキャンペーンを実行するメソッドには、引数としてキャンペーン識別子を渡す必要があります。 キャンペーンのNQL ID(<し>推奨)およびキャンペーンUIDの両方を使用できます。
リモートアクションにキャンペーン識別子を渡すには、リモートアクションのスクリプトで必要なそれぞれのキャンペーンのパラメーターを宣言します。 リモートアクションを編集する際、実際の値としてNQL ID(<し>またはUID)をパラメーターに使用します。
キャンペーンのNQL IDまたはUIDを取得する方法の詳細は、キャンペーンをトリガーするドキュメントを参照してください。
リモートアクションのスクリプトからキャンペーンを実行する
キャンペーンとインタラクションするには、リモートアクションスクリプトは、Collectorと共に社員のデバイスにインストールされた.NETアセンブリ(<し>nxtcampaignaction.dll
)をロードする必要があります。 このアセンブリには、キャンペーンの実行を制御し、社員の回答を取得するメソッドを提供する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
型の応答オブジェクトに基づいて、キャンペーンのステータスを反映した文字列を返すメソッドです。 ステータスの可能な値:
fully:社員がキャンペーンの質問に完全に回答しました。
declined:社員がキャンペーンへの参加を辞退しました。
postponed:社員がキャンペーンへの参加に同意しました。
timeout:システムが社員の回答が完了する前にキャンペーンをタイムアウトしました。
connectionfailed:Collectorコンポーネント間の通信の技術的エラーにより、キャンペーン通知を制御するCollectorコンポーネントに接続できませんでした。
notificationfailed:スクリプトがキャンペーンを正常に表示できませんでした。以下の理由のいずれか:
キャンペーンが存在しないか未発表のため、プラットフォームからキャンペーンの定義を取得できませんでした。
非緊急対応キャンペーンは、Collectorの集中保護または「取り込み禁止」ルールのため表示できません。 詳細は、キャンペーンの受信率を制限するドキュメントを参照してください。
string[] GetResponseAnswer(NxTrayResp response, string questionLabel)
NxTrayResp
型の応答オブジェクトとキャンペーン内の質問を識別するラベルを指定すると、社員の回答を返すメソッドです。
単一回答の質問の場合、返された文字列配列には一つの要素しかありません。
複数回答の質問の場合、返される文字列配列には、社員が選択した回答の数だけ要素が含まれます。 任意の自由テキストは無視されます。
社員がキャンペーンに完全に回答していない場合、たとえばステータスがfully
でない場合、返される文字列配列は空になります。 任意の自由記述テキストは無視されます。
セキュリティ上の理由から、セルフヘルプシナリオのためのリモートアクションは、複数回答または意見スケール質問のオプションの自由テキスト回答を無視します。 セルフヘルプシナリオで使用すべきキャンペーンにオプションの自由テキスト回答を含めることは役立ちません。
スクリプトのエンコード
PowerShellスクリプトファイルは、バイトオーダー・マーク(<し>BOM)付きのUTF-8でエンコードされていなければなりません。 BOMはファイルの冒頭に存在する必要があるUnicode文字であり、そのUTF-8での表現は3バイトの16進シーケンスEF BB BF
です。
各コード行はWindowsではCR+LF
という文字シーケンスで終わらなければなりません。
適切なエンコーディングを確保して、エラーやスクリプトの不具合を回避してください。
コード例
キャンペーンの呼び出し
この例では、リモートアクションが基本的なcampaign呼び出しをIDで実行し、成功した場合にはステータスメッセージを、失敗した場合にはエラーメッセージを出力します。
$result = [Nxt.CampaignAction]::RunCampaign($CampaignUid, $maxWaitTimeinSeconds)
$status = [Nxt.CampaignAction]::GetResponseStatus($result)
if ($status -eq "fully") {
Write-Output "キャンペーンが成功しました"
} else {
Write-Output "ステータスは $status です"
}
キャンペーンの応答へのアクセス
この例では、リモートアクションがキャンペーンの回答データを呼び出し、それを配列として出力します。 各回答は対応する番号付きオプションで表されます。 PowerShellは0からn-1のインデックスを使用していることに注意してください。
# キャンペーン応答を取得する関数
function Get-CampaignResponse ([string]$CampaignId) {
return [nxt.campaignaction]::RunCampaign($CampaignId, $CAMPAIGN_TIMEOUT)
}
# キャンペーンのステータスを取得する関数
function Get-CampaignResponseStatus ($Response) {
return [nxt.campaignaction]::GetResponseStatus($Response)
}
# 応答の回答を取得する関数
function Get-CampaignResponseAnswer ($Response, [string]$QuestionName) {
return [nxt.campaignaction]::GetResponseAnswer($Response, $QuestionName)[0]
}
# キャンペーン応答の取得
$campaignResponse = $null
$campaignResponse = Get-CampaignResponse -CampaignId $campaignId
# キャンペーンステータスの取得
$status = $null
$status = Get-CampaignResponseStatus -Response $campaignResponse
Write-Host "応答ステータスは $status です"
# 応答の回答の取得
$answersArray = $null
$answer = Get-CampaignResponseAnswer -Response $campaignResponse -QuestionName "Question1"
Write-Host "答えは $answer です"
タイムアウト付きでキャンペーンを実行
この例では、リモートアクションが指定した時間後にタイムアウトし終了するcampaignを秒を入力として実行します。
# タイムアウト付きでキャンペーンを実行
# タイムアウトは秒単位 (100秒または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 "キャンペーンが成功しました"
} else {
Write-Output "ステータスは $status です"
}
ノンブロッキングキャンペーンの実行
この例では、リモートアクションがユーザー入力を必要とせず、キャンペーンをトリガーした後に実行を続けます。 ユーザーはいつでもキャンペーンを終了することができます。 これはデータを取得するのではなく、主にユーザーに情報を提供するために使われます。
##### 非ブロッキングキャンペーンの実行 #####
$mycampaignId = "#my_campaign_nql_id"
function Invoke-OperationCompletedCampaign ([string]$CampaignId) {
[nxt.campaignaction]::RunStandAloneCampaign($CampaignId)
}
Invoke-OperationCompletedCampaign -CampaignId $mycampaignId
特定のキャンペーンレスポンスでアプリケーションを開く
この例では、リモートアクションスクリプトが、Collectorとデバイスにインストールされた.dllファイルをロードし、Collectorとリモートアクション実行の橋渡しとして機能します。 PowerShellスクリプトからCollectorにコマンドを発行し、[Nxt.CampaignAction]
で始まる専門機能を使用できるようにします。
リモートアクションはNxt.CampaignAction]::RunCampaign
関数を使用してキャンペーンを実行し、キャンペーンIDと秒単位のタイムアウトを入力として受け取ります。 次に、ユーザーの応答または応答のない状態を収集し、そのデータを使用してステータスを決定します。 ユーザーがyes
と答えた場合、リモートアクションがプロセスを開始します。この場合、Notepadを起動します。
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") {
# ユーザーが確認しましたので、いくつかのアクションを実行します:
Start-Process notepad.exe
}
}
特定のアプリケーションがデバイス上にあるか確認する
この例では、リモートアクションが入力として提供したアプリケーション名がデバイス上に存在するかKanopyを使用して確認します:
#
# 入力パラメータの定義
#
param(
[Parameter(Mandatory = $true)][string]$application_name
)
# パラメータ定義の終了
$env:Path = 'C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\'
#
# 定数の定義
#
$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
#
# 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
}
#
# テンプレート関数
#
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()
}
#
# 入力パラメータの検証
#
function Test-InputParameter ([hashtable]$InputParameters) {
Test-StringNullOrEmpty `
-ParamName 'application_name' `
-ParamValue $InputParameters.application_name
}
#
# アプリケーション管理
#
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管理
#
function Update-EngineOutputVariables ([bool]$applicationPresent) {
[nxt]::WriteOutputBool('application_present', $applicationPresent)
}
#
# メインスクリプトの流れ
#
[environment]::Exit((Invoke-Main -InputParameters $MyInvocation.BoundParameters))
特定のアプリケーションがデバイスに存在するか確認する: エラーハンドリング
この例では、リモートアクションがアプリケーションログパスとエラーコードを入力として使用し、指定されたエラーコードのログを解析します。 このコードが存在する場合、エラーメッセージを出力します:
#
# 入力パラメータの定義
#
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 = '[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
$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 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()
}
function Test-ParamIsInteger ([string]$ParamName, [string]$ParamValue) {
$intValue = $ParamValue -as [int]
if ([string]::IsNullOrEmpty($ParamValue) -or $null -eq $intValue) {
throw "$($ERROR_EXCEPTION_TYPE.Input) Error in parameter '$ParamName'. '$ParamValue' is not an integer. "
}
}
#
# 入力パラメータの検証
#
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 is not a valid path or is not accessible."
}
}
#
# アプリケーション管理
#
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 Output management
#
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))
特定のアプリケーションがデバイスに存在するか確認する: エラー修復
この例では、Remote ActionがCampaignsを使用して、ユーザーにアプリケーションがすでに実行中である場合は再起動が必要であることを知らせるか、実行中でない場合は起動します:
#
# 入力パラメータの定義
#
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 = '[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
$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
#
# Mainを呼び出す
#
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 not found. "
}
if (-not (Test-Path -Path $CAMPAIGN_DLL_PATH)) {
throw "$($ERROR_EXCEPTION_TYPE.Environment) Nexthink Campaign DLL not found. "
}
Add-Type -Path $REMOTE_ACTION_DLL_PATH
Add-Type -Path $CAMPAIGN_DLL_PATH
}
function Test-RunningAsInteractiveUser {
if (Confirm-CurrentUserIsLocalSystem) {
throw "$($ERROR_EXCEPTION_TYPE.Environment) This script must be run as 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) 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-CampaignID ([string]$ParamName, [string]$ParamValue) {
if ([string]::IsNullOrEmpty($ParamValue)) {
throw "$($ERROR_EXCEPTION_TYPE.Input) Error in parameter '$ParamName'. Value cannot be null or empty. "
}
if (-not ($ParamValue -as [guid]) -and ($ParamValue -notmatch $NQL_ID_FORMAT_REGEX)) {
throw "$($ERROR_EXCEPTION_TYPE.Input) Error in parameter '$ParamName'. Only UID or NQL ID values are accepted. "
}
}
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 "Calling $($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 "Unexpected answer from the user: $answer. "
}
}
'timeout' { throw "Timeout on getting an answer from the user. " }
'declined' {throw "Campaign declined by user. " }
'postponed' {throw "Campaign postponed by user. " }
'connectionfailed' { throw "Unable to connect to the Collector component that controls campaign notifications. " }
'notificationfailed' { throw "Unable to notify the Collector component that controls campaign notifications. " }
default { throw "Failed to handle campaign response: $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 "The Kanopy process was stopped successfully."
} catch {
Invoke-OperationCompletedCampaign -CampaignId $InputParameters.inform_failure_campaign_id
throw "Failed to stop the process Kanopy. Error: $_"
}
Start-Sleep -Seconds 2
try {
Start-Process -Name $KANOPY_APPLICATION_PROCESS_NAME -Force -ErrorAction Stop | Out-Null
Write-StatusMessage -Message "The Kanopy process was started successfully."
} catch {
Invoke-OperationCompletedCampaign -CampaignId $InputParameters.inform_failure_campaign_id
throw "Failed to start the process Kanopy after stopping it. Error: $_"
}
Invoke-OperationCompletedCampaign -CampaignId $InputParameters.final_campaign_id
} else {
Write-StatusMessage -Message "The user declined the remediation action."
}
} else {
Write-StatusMessage -Message "The Kanopy process was not running. Application will be started."
try {
Start-Process -FilePath $KANOPY_APPLICATION_EXECUTABLE_PATH -ErrorAction Stop
} catch {
throw "Failed to start the process Kanopy. Error: $_"
}
Start-Sleep -Seconds 2
$processCheck = Get-Process -Name $KANOPY_APPLICATION_PROCESS_NAME -ErrorAction SilentlyContinue
if ($processCheck) {
Write-StatusMessage -Message "The Kanopy process was started successfully."
} else {
throw "The Kanopy process was not started successfully."
}
}
}
#
# メインスクリプトの流れ
#
[environment]::Exit((Invoke-Main -InputParameters $MyInvocation.BoundParameters))
スクリプトに署名する
証明書を取得する
PowerShellスクリプトに署名するには、Set-Authenticode コマンドを次のように使用します:
PFXファイル:
$cert = Get-PfxCertificate -FilePath C:\Test\Mysign.pfx
証明書を使用して、リモートアクション用スクリプト、例えばremoteaction.ps1
に署名します。 証明書の有効期限が切れた後も機能するようにタイムスタンプを追加します。 以下の例ではDigiCertのタイムスタンプサーバーを使用しています:
Set-AuthenticodeSignature -FilePath .\remoteaction.ps1 -Certificate $cert -IncludeChain All -TimestampServer "http://timestamp.digicert.com"
(オプション)スクリプト内の署名を確認します。
Get-AuthenticodeSignature .\remoteaction.ps1 -Verbose | fl
プライベート認証局(CA)を使用する場合、キャッシュによるサーバーの過負荷を避けるため、最良のOCSPプラクティスを使用していることを確認してください。
エンドポイントへの証明書の展開
デフォルトのポリシー(signed_trusted_or_nexthink
)では、追加の設定なしにデバイスでNexthinkライブラリからの公式なリモートアクションを実行できます。
より厳格なsigned_trusted
ポリシーを使用する場合、ライブラリおよびシステムスクリプトを独自の証明書で再署名するか、Nexthinkコード署名証明書をローカル コンピュータ > 信頼された発行元の証明書ストアに配布できます。
コード署名証明書をWindowsのローカル コンピュータ > 信頼された発行元ストアに追加しない場合、システムは次のエラーを生成します: リモートアクションを実行できませんでした:スクリプト署名が無効であるか、証明書が信頼されていません。
プライベートCAによって生成された証明書のルート証明書がWindowsのローカル コンピュータの信頼されたルート証明機関の証明書ストアにすでに存在しない場合、追加することを確認してください。
スクリプト署名に中間証明書を使用した場合、中間証明書チェーン全体をWindowsのローカル コンピュータの中間証明機関の証明書ストアに含めます:
管理者としてMicrosoft Windowsにログインします。
プログラムがデバイスを変更することを許可するために、はいをクリックします。
左側のリストで、望む証明書ストアの名前、例えば信頼された発行元を右クリックします。
コンテキストメニューからすべてのタスク > インポート... を選択して、証明書インポートウィザードを開始します。
次へをクリックして、以下のストアにすべての証明書を配置ダイアログウィンドウで提案された証明書ストアを受け入れます。
インポートする証明書を確認し、完了をクリックします。
スクリプトの維持管理
比較と検証
リモートアクションスクリプトをデプロイする前に、Nexthinkによって準備された他のスクリプトと比較することが可能です。 このステップは任意ですが、初めてスクリプトを準備する場合に推奨されます。
Nexthinkライブラリで、コンテンツを選択します。
ターゲットのオペレーティングシステムに一致するNexthinkライブラリから直接インストールされたリモートアクションスクリプトを選択します。
スクリプトをエクスポートし、その構文をあなたのスクリプトのものと比較します。
エラーハンドリング
Nexthinkは、スクリプトを実行したPowerShellプロセスの戻り値に基づいて、リモートアクションの実行が成功したかどうかを検出します:
終了コードの値が0の場合、正常に実行されたことを示します。
PowerShell内の未処理の例外は、適切な終了コードを返すことなくスクリプトを終了させる可能性があります。 予期しないエラーを処理するために、すべてのスクリプトの冒頭に次のコードスニペットを追加することをNexthinkは推奨しています:
trap {
$host.ui.WriteErrorLine($_.ToString())
exit 1
}
必須パラメータの宣言後、必要なDLL依存項目を含む部分の直下にこのデフォルトのエラーハンドラーを配置します。
パフォーマンス測定
お使いのスクリプトのパフォーマンスとリソース消費を測定するには、Collectorの構成ツールを使用して、Collectorのデバッグモードのログをオンにしてください:
nxtcfg.exe /s logmode=2
出力はnxtcod.logファイルに保存されます。