mcpField
A
asset-management

asset-management

github.com/serac-labsクラウド・DevOps7782 回インストール9 週間前に更新
リポジトリ

asset-management について

ServiceNow でハードウェアとソフトウェア資産のライフサイクルを追跡するスキルです。資産レコード、ソフトウェアライセンスの割り当て、検出した構成アイテムとの照合、在庫レポートを ES5 の例とともに扱います。

主な機能

  1. 01ハードウェア資産レコード
  2. 02ソフトウェアライセンスの記録と割り当て
  3. 03CI と資産の照合
  4. 04ライフサイクル状態の追跡
  5. 05在庫概要レポート
  6. 06ES5 スクリプト例

活用シーン

  1. 01ハードウェアのライフサイクル管理
  2. 02ユーザーへのソフトウェアライセンス割り当て
  3. 03検出した CI と資産の照合
  4. 04状態や場所ごとの在庫集計

MCP 対応

資産とライセンスの検索、CMDB 内の構成アイテムの検索、スクリプトのテスト、資産設定の検索に使う MCP ツールを記載しています。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/serac-labs/serac --skill asset-management

インストール後、Claude Code で/asset-managementを入力して読み込まれたか確認します。

詳細

ServiceNow の資産管理

資産管理では、ハードウェアおよびソフトウェア資産のライフサイクル全体を追跡します。

資産アーキテクチャ

Asset (alm_asset)
    ├── Hardware Asset (alm_hardware)
    │   └── Consumable (alm_consumable)
    └── License (alm_license)

CI (cmdb_ci) ←→ Asset (alm_asset)
    ↑
    One CI can have multiple assets over time

主なテーブル

テーブル用途
alm_asset基本資産テーブル
alm_hardwareハードウェア資産
alm_licenseソフトウェアライセンス
alm_consumable消耗品資産
ast_contract資産契約
cmdb_ci構成アイテム

ハードウェア資産(ES5)

ハードウェア資産の作成
// Create hardware asset (ES5 ONLY!)
var asset = new GlideRecord("alm_hardware")
asset.initialize()

// Basic info
asset.setValue("display_name", "Dell Latitude 5520")
asset.setValue("asset_tag", "ASSET-" + generateAssetTag())
asset.setValue("serial_number", "SN123456789")

// Model
asset.setValue("model", getModelSysId("Dell Latitude 5520"))
asset.setValue("model_category", getModelCategorySysId("Laptop"))

// Status and substatus
asset.setValue("install_status", 1) // Installed
asset.setValue("substatus", "in_use")

// Assignment
asset.setValue("assigned_to", userSysId)
asset.setValue("assigned", new GlideDateTime())
asset.setValue("location", locationSysId)
asset.setValue("department", departmentSysId)

// Financial
asset.setValue("cost", 1299.99)
asset.setValue("cost_center", costCenterSysId)

// Dates
asset.setValue("purchase_date", "2024-01-15")
asset.setValue("warranty_expiration", "2027-01-15")

// Link to CI if exists
asset.setValue("ci", cmdbCiSysId)

asset.insert()
資産ライフサイクルの状態
// Asset install_status values
var ASSET_STATUS = {
  INSTALLED: 1, // In use
  ON_ORDER: 2, // Ordered, not received
  IN_STOCK: 6, // In inventory
  IN_TRANSIT: 7, // Being shipped
  IN_MAINTENANCE: 8, // Under repair
  RETIRED: 9, // End of life
  DISPOSED: 10, // Disposed of
}

// Transition asset status (ES5 ONLY!)
function transitionAssetStatus(assetSysId, newStatus, notes) {
  var asset = new GlideRecord("alm_hardware")
  if (!asset.get(assetSysId)) {
    return { success: false, message: "Asset not found" }
  }

  var currentStatus = parseInt(asset.getValue("install_status"), 10)

  // Validate transition
  var validTransitions = {
    2: [6, 7], // On Order -> In Stock, In Transit
    7: [6, 1], // In Transit -> In Stock, Installed
    6: [1, 7, 9], // In Stock -> Installed, In Transit, Retired
    1: [8, 6, 9], // Installed -> In Maintenance, In Stock, Retired
    8: [1, 6, 9], // In Maintenance -> Installed, In Stock, Retired
    9: [10], // Retired -> Disposed
  }

  if (!validTransitions[currentStatus] || validTransitions[currentStatus].indexOf(newStatus) === -1) {
    return {
      success: false,
      message: "Invalid transition from " + currentStatus + " to " + newStatus,
    }
  }

  // Update status
  asset.setValue("install_status", newStatus)

  // Handle specific transitions
  if (newStatus === 1) {
    asset.setValue("installed", new GlideDateTime())
  } else if (newStatus === 9) {
    asset.setValue("retired", new GlideDateTime())
    asset.setValue("assigned_to", "")
  } else if (newStatus === 10) {
    asset.setValue("disposed", new GlideDateTime())
  }

  // Add work note
  if (notes) {
    asset.work_notes = notes
  }

  asset.update()

  return { success: true, asset_tag: asset.getValue("asset_tag") }
}

ソフトウェアライセンス(ES5)

ライセンスレコードの作成
// Create software license (ES5 ONLY!)
var license = new GlideRecord("alm_license")
license.initialize()

// Basic info
license.setValue("display_name", "Microsoft Office 365 E3")
license.setValue("product", getProductSysId("Microsoft Office 365"))
license.setValue("license_type", "per_user") // per_user, per_device, site, enterprise

// Quantities
license.setValue("rights", 500) // Total licenses purchased
license.setValue("used", 0) // Will be calculated
license.setValue("remaining", 500) // Will be calculated

// Dates
license.setValue("start_date", "2024-01-01")
license.setValue("end_date", "2024-12-31")

// Cost
license.setValue("cost", 25000.0)
license.setValue("cost_per_unit", 50.0)

// Vendor
license.setValue("vendor", vendorSysId)
license.setValue("contract", contractSysId)

license.insert()
ライセンスの割り当て
// Allocate license to user (ES5 ONLY!)
function allocateLicense(licenseSysId, userSysId) {
  var license = new GlideRecord("alm_license")
  if (!license.get(licenseSysId)) {
    return { success: false, message: "License not found" }
  }

  // Check availability
  var remaining = parseInt(license.getValue("remaining"), 10)
  if (remaining <= 0) {
    return { success: false, message: "No licenses available" }
  }

  // Check if user already has this license
  var existing = new GlideRecord("alm_entitlement_user")
  existing.addQuery("licensed_by", licenseSysId)
  existing.addQuery("user", userSysId)
  existing.query()

  if (existing.hasNext()) {
    return { success: false, message: "User already has this license" }
  }

  // Create entitlement
  var entitlement = new GlideRecord("alm_entitlement_user")
  entitlement.initialize()
  entitlement.setValue("licensed_by", licenseSysId)
  entitlement.setValue("user", userSysId)
  entitlement.setValue("allocated", new GlideDateTime())
  entitlement.insert()

  // Update license counts
  updateLicenseCounts(licenseSysId)

  return {
    success: true,
    message: "License allocated",
    entitlement: entitlement.getUniqueValue(),
  }
}

function updateLicenseCounts(licenseSysId) {
  var license = new GlideRecord("alm_license")
  if (!license.get(licenseSysId)) return

  // Count allocations
  var ga = new GlideAggregate("alm_entitlement_user")
  ga.addQuery("licensed_by", licenseSysId)
  ga.addAggregate("COUNT")
  ga.query()

  var used = 0
  if (ga.next()) {
    used = parseInt(ga.getAggregate("COUNT"), 10)
  }

  var rights = parseInt(license.getValue("rights"), 10)

  license.setValue("used", used)
  license.setValue("remaining", rights - used)
  license.update()
}

資産検出との連携(ES5)

検出された CI を資産と照合する
// Match discovered CI to existing asset (ES5 ONLY!)
function matchCIToAsset(ciSysId) {
  var ci = new GlideRecord("cmdb_ci_computer")
  if (!ci.get(ciSysId)) {
    return null
  }

  var serialNumber = ci.getValue("serial_number")
  var assetTag = ci.getValue("asset_tag")

  // Try to find matching asset
  var asset = new GlideRecord("alm_hardware")

  // Match by serial number first
  if (serialNumber) {
    asset.addQuery("serial_number", serialNumber)
    asset.query()
    if (asset.next()) {
      return linkAssetToCI(asset, ci)
    }
  }

  // Match by asset tag
  if (assetTag) {
    asset = new GlideRecord("alm_hardware")
    asset.addQuery("asset_tag", assetTag)
    asset.query()
    if (asset.next()) {
      return linkAssetToCI(asset, ci)
    }
  }

  // No match - create new asset
  return createAssetFromCI(ci)
}

function linkAssetToCI(asset, ci) {
  asset.setValue("ci", ci.getUniqueValue())
  asset.update()

  ci.setValue("asset", asset.getUniqueValue())
  ci.update()

  return asset.getUniqueValue()
}

function createAssetFromCI(ci) {
  var asset = new GlideRecord("alm_hardware")
  asset.initialize()
  asset.setValue("display_name", ci.getDisplayValue())
  asset.setValue("serial_number", ci.getValue("serial_number"))
  asset.setValue("asset_tag", ci.getValue("asset_tag"))
  asset.setValue("model", ci.getValue("model_id"))
  asset.setValue("ci", ci.getUniqueValue())
  asset.setValue("install_status", 1)

  var assetSysId = asset.insert()

  ci.setValue("asset", assetSysId)
  ci.update()

  return assetSysId
}

資産レポート(ES5)

資産インベントリの概要
// Get asset inventory summary (ES5 ONLY!)
function getAssetInventorySummary() {
  var summary = {
    by_status: {},
    by_category: {},
    by_location: {},
    total_value: 0,
  }

  // By status
  var ga = new GlideAggregate("alm_hardware")
  ga.addAggregate("COUNT")
  ga.addAggregate("SUM", "cost")
  ga.groupBy("install_status")
  ga.query()

  while (ga.next()) {
    var status = ga.install_status.getDisplayValue()
    summary.by_status[status] = {
      count: parseInt(ga.getAggregate("COUNT"), 10),
      value: parseFloat(ga.getAggregate("SUM", "cost")) || 0,
    }
    summary.total_value += summary.by_status[status].value
  }

  // By model category
  ga = new GlideAggregate("alm_hardware")
  ga.addAggregate("COUNT")
  ga.groupBy("model_category")
  ga.query()

  while (ga.next()) {
    var category = ga.model_category.getDisplayValue() || "Uncategorized"
    summary.by_category[category] = parseInt(ga.getAggregate("COUNT"), 10)
  }

  // By location
  ga = new GlideAggregate("alm_hardware")
  ga.addQuery("install_status", 1) // Only installed
  ga.addAggregate("COUNT")
  ga.groupBy("location")
  ga.query()

  while (ga.next()) {
    var location = ga.location.getDisplayValue() || "Unknown"
    summary.by_location[location] = parseInt(ga.getAggregate("COUNT"), 10)
  }

  return summary
}

MCP ツールとの連携

利用可能なツール
ツール用途
snow_query_table資産とライセンスをクエリする
snow_cmdb_searchCMDB で CI を検索する
snow_execute_script資産スクリプトをテストする
snow_artifact_manage資産構成を検索する(action: find)
ワークフローの例
// 1. Query hardware assets
await snow_query_table({
  table: "alm_hardware",
  query: "install_status=1",
  fields: "asset_tag,display_name,assigned_to,location,model",
})

// 2. Check license compliance
await snow_execute_script({
  script: `
        var license = new GlideRecord('alm_license');
        license.addQuery('remainingRELATIVELT@integer@0');
        license.query();
        while (license.next()) {
            gs.info('Over-allocated: ' + license.display_name);
        }
    `,
})

// 3. Find assets nearing warranty expiration
await snow_query_table({
  table: "alm_hardware",
  query: "warranty_expirationBETWEENjavascript:gs.beginningOfToday()@javascript:gs.daysAgoEnd(-30)",
  fields: "asset_tag,display_name,warranty_expiration,assigned_to",
})

ベストプラクティス

  1. 資産タグ - 一意でスキャン可能な識別子
  2. ライフサイクルの追跡 - すべての状態変更を追跡します
  3. CI のリンク - 資産を CMDB に接続します
  4. ライセンスコンプライアンス - 割り当て数と権利数を監視します
  5. 保証の追跡 - 有効期限前にアラートを送信します
  6. 財務情報の正確性 - コストデータを維持します
  7. 定期監査 - 実物の在庫を確認します
  8. ES5 のみ - 最新の JavaScript 構文は使用しません

コメント