ServiceNowの問題チケットに添付ファイルを移行する方法

## ✅ 前提整理

| 項目 | 内容 |
|------|------|
| 添付対象 | `problem` テーブル |
| 各フォルダ名 | 特定のキーワード（例：PRB0012345や「network」など）<br>→ これをServiceNow APIで検索キーに使う |
| ファイル構造 | 例：`C:\UploadRoot\PRB0012345\file1.pdf` や `C:\UploadRoot\network\log.txt`
| 使用API | `GET /api/now/table/problem?sysparm_query=...` でレコード検索 → `POST /attachment/file` で添付

---

## 🐋 PowerShellスクリプト案（キーワード→sys_id取得→添付）

```powershell
# 認証・インスタンス設定
$Username = "your_username"
$Password = "your_password"
$Base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$Username:$Password"))
$Instance = "your_instance.service-now.com"
$TableName = "problem"

# ルートディレクトリ（フォルダ名＝キーワード）
$RootFolder = "C:\UploadRoot"

# プロキシ対応クライアント
$Handler = New-Object System.Net.Http.HttpClientHandler
$Handler.UseProxy = $true
$Handler.Proxy = [System.Net.WebRequest]::GetSystemWebProxy()
$Handler.Proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials

$Client = New-Object System.Net.Http.HttpClient($Handler)
$Client.DefaultRequestHeaders.Authorization = [System.Net.Http.Headers.AuthenticationHeaderValue]::new("Basic", $Base64AuthInfo)
$Client.DefaultRequestHeaders.Accept.Add([System.Net.Http.Headers.MediaTypeWithQualityHeaderValue]::Parse("application/json"))

# フォルダごとに処理
Get-ChildItem -Path $RootFolder -Directory | ForEach-Object {
    $Keyword = $_.Name
    $FolderPath = $_.FullName

    # 🔍 キーワードでレコード検索（例では「number」フィールドと一致）
    $Query = "number=$Keyword"  # 例: PRB0012345と一致
    $SearchUrl = "https://$Instance/api/now/table/$TableName?sysparm_query=$Query&sysparm_limit=1"
    $SearchResult = $Client.GetAsync($SearchUrl).Result
    $JsonResult = $SearchResult.Content.ReadAsStringAsync().Result | ConvertFrom-Json

    if ($JsonResult.result.Count -eq 0) {
        Write-Host "⚠ レコードが見つかりません: $Keyword"
        return
    }

    $RecordSysId = $JsonResult.result[0].sys_id
    Write-Host "`n📁 [$Keyword] → sys_id: $RecordSysId"

    # ファイルごとに添付
    Get-ChildItem -Path $FolderPath -File | ForEach-Object {
        $FilePath = $_.FullName
        $FileName = $_.Name

        $Url = "https://$Instance/api/now/attachment/file?table_name=$TableName&table_sys_id=$RecordSysId&file_name=$FileName"

        $FileBytes = [System.IO.File]::ReadAllBytes($FilePath)
        $FileContent = New-Object System.Net.Http.ByteArrayContent($FileBytes)
        $FileContent.Headers.ContentDisposition = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new("form-data")
        $FileContent.Headers.ContentDisposition.Name = '"file"'
        $FileContent.Headers.ContentDisposition.FileName = '"' + $FileName + '"'
        $FileContent.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::Parse("application/octet-stream")

        $Multipart = New-Object System.Net.Http.MultipartFormDataContent
        $Multipart.Add($FileContent)

        $Response = $Client.PostAsync($Url, $Multipart).Result

        if ($Response.IsSuccessStatusCode) {
            Write-Host "✅ $FileName を添付しました"
        } else {
            Write-Host "❌ $FileName の添付失敗"
            $Response.Content.ReadAsStringAsync().Result
        }
    }
}
```

---

## ✅ カスタマイズ例

| 目的 | 方法 |
|------|------|
| タイトルで部分一致検索 | `$Query = "short_descriptionLIKE$Keyword"` |
| 番号で前方一致検索 | `$Query = "numberSTARTSWITH$Keyword"` |
| キーワードをファイル名から抽出したい | `Split-Path $FilePath -LeafBase` などで処理可 |
| レコードが複数一致する場合の処理 | `$JsonResult.result[0]` をループに変えることで対応可 |

---

## 🧪 テストのおすすめ手順

1. 1件のフォルダで動作確認（例：`C:\UploadRoot\PRB0012345`）
2. ServiceNowで該当レコードを開き、添付ファイルが正しく表示されているか確認
3. 問題なければ複数フォルダへ展開

---

必要であれば、  
- キーワードをCSVから読み取る形式  
- 添付のログ出力保存  
なども対応できます。必要な方向性があればぜひ教えてください！