# JIRA Module
# 更新日: 2025/12/23
# Jira APIアクセス用の認証ヘッダとプロキシ情報を生成
# 引 数: JiraのベースURL(例: https://xxx.atlassian.net)。
# 戻り値: PSCustomObject(Headers, ProxyUri)
function New-JiraAuthContext {
param(
[Parameter(Mandatory=$true)]
[string]$JiraBase
)
$scriptRoot = $PSScriptRoot
if (-not $scriptRoot) {$scriptRoot = (Get-Location).Path}
$keyFile = Join-Path -Path $scriptRoot -ChildPath "JiraAPI.key"
if (-not (Test-Path $keyFile)) {throw "ファイルが見つかりません: $keyFile"}
$credential = Import-Csv -Path $keyFile | Select-Object -First 1
$email = $credential.User
$apiToken = $credential.Key
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$basic = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("${email}:${apiToken}"))
$headers = @{
"Authorization" = "Basic $basic"
"Accept" = "application/json"
}
$proxy = [System.Net.WebRequest]::GetSystemWebProxy()
$proxyUri = $proxy.GetProxy([Uri]::new($JiraBase))
[PSCustomObject]@{
Headers = $headers
ProxyUri = $proxyUri
}
}
# ServiceDeskのキューURLから ServiceDeskId / RequestTypeId / QueueId を特定して返します。
# 引 数: Jira Service ManagementのキューURL。
# 例: https://xxx.atlassian.net/jira/servicedesk/projects/AAA/queues/custom/833
# 戻り値: PSCustomObject(ServiceDeskId, RequestTypeId, QueueId, Name, Description)
function Get-IdsFromUrl {
param(
[Parameter(Mandatory=$true)]
[string]$Url,
[Parameter(Mandatory=$true)]
[pscustomobject]$AuthContext
)
$uri = [Uri]$Url
$jiraBaseFromUrl = "{0}://{1}" -f $uri.Scheme, $uri.Host
if (-not $AuthContext.Headers) {
throw "AuthContext.Headers が未設定です。New-JiraAuthContext の戻り値を指定してください。"
}
if (-not $AuthContext.ProxyUri) {
throw "AuthContext.ProxyUri が未設定です。New-JiraAuthContext の戻り値を指定してください。"
}
$m = [regex]::Match(
$uri.AbsolutePath,
"(?:/jira)?/servicedesk/projects/(?<ProjectKey>[^/]+)/queues/(?<QueueType>[^/]+)/(?<QueueId>[0-9]+)(?:/)?$",
[System.Text.RegularExpressions.RegexOptions]::IgnoreCase
)
if (-not $m.Success) {
throw "URL形式が想定と違います: $Url"
}
$projectKey = $m.Groups["ProjectKey"].Value
$queueId = $m.Groups["QueueId"].Value
$headers = $AuthContext.Headers
$proxyToUse = $AuthContext.ProxyUri
$serviceDeskId = $null
$sdStart = 0
$sdLimit = 50
do {
$sdUrl = "$jiraBaseFromUrl/rest/servicedeskapi/servicedesk?start=$sdStart&limit=$sdLimit"
try {
$sdResponse = Invoke-RestMethod -Method Get -Uri $sdUrl -Headers $headers -Proxy $proxyToUse -ProxyUseDefaultCredentials
} catch {
throw "サービスデスク一覧の取得に失敗しました: $($_.Exception.Message)"
}
foreach ($sd in $sdResponse.values) {
if ($sd.projectKey -eq $projectKey) {
$serviceDeskId = [string]$sd.id
break
}
}
if ($serviceDeskId) {
break
}
if ($sdResponse.PSObject.Properties.Name -contains "isLastPage") {
if ($sdResponse.isLastPage) {
break
}
} else {
break
}
$sdStart += $sdLimit
} while ($true)
if (-not $serviceDeskId) {
throw "ProjectKey '$projectKey' に対応するServiceDeskIdが見つかりません。"
}
$queueIssuesUrl = "$jiraBaseFromUrl/rest/servicedeskapi/servicedesk/$serviceDeskId/queue/$queueId/issue?start=0&limit=1"
try {
$queueIssues = Invoke-RestMethod -Method Get -Uri $queueIssuesUrl -Headers $headers -Proxy $proxyToUse -ProxyUseDefaultCredentials
} catch {
throw "キューのリクエスト取得に失敗しました: $($_.Exception.Message)"
}
$requestTypeId = $null
$requestTypeName = $null
$requestTypeDescription = $null
if ($queueIssues -and $queueIssues.values -and $queueIssues.values.Count -gt 0) {
$first = $queueIssues.values[0]
$issueKey = $null
if ($first.PSObject.Properties.Name -contains "issueKey") {
$issueKey = $first.issueKey
} elseif ($first.PSObject.Properties.Name -contains "key") {
$issueKey = $first.key
} elseif ($first.PSObject.Properties.Name -contains "issueId") {
$issueKey = $first.issueId
}
if ($issueKey) {
$reqUrl = "$jiraBaseFromUrl/rest/servicedeskapi/request/$issueKey"
try {
$req = Invoke-RestMethod -Method Get -Uri $reqUrl -Headers $headers -Proxy $proxyToUse -ProxyUseDefaultCredentials
} catch {
throw "リクエスト詳細の取得に失敗しました: $($_.Exception.Message)"
}
if ($req.PSObject.Properties.Name -contains "requestTypeId") {
$requestTypeId = [string]$req.requestTypeId
} elseif ($req.requestType -and ($req.requestType.PSObject.Properties.Name -contains "id")) {
$requestTypeId = [string]$req.requestType.id
}
if ($req.requestType) {
if ($req.requestType.PSObject.Properties.Name -contains "name") {
$requestTypeName = [string]$req.requestType.name
}
if ($req.requestType.PSObject.Properties.Name -contains "description") {
$requestTypeDescription = [string]$req.requestType.description
}
}
}
}
if ($requestTypeId -and ((-not $requestTypeName) -or (-not $requestTypeDescription))) {
$rtUrl = "$jiraBaseFromUrl/rest/servicedeskapi/servicedesk/$serviceDeskId/requesttype/$requestTypeId"
try {
$rt = Invoke-RestMethod -Method Get -Uri $rtUrl -Headers $headers -Proxy $proxyToUse -ProxyUseDefaultCredentials
} catch {
$rt = $null
}
if ($rt) {
if (-not $requestTypeName) {
if ($rt.PSObject.Properties.Name -contains "name") {
$requestTypeName = [string]$rt.name
}
}
if (-not $requestTypeDescription) {
if ($rt.PSObject.Properties.Name -contains "description") {
$requestTypeDescription = [string]$rt.description
}
}
}
}
if (-not $requestTypeId) {
throw "RequestTypeIdを特定できません(キューにリクエストがない、または権限不足の可能性があります)。"
}
[PSCustomObject]@{
ServiceDeskId = $serviceDeskId
RequestTypeId = $requestTypeId
QueueId = $queueId
Name = $requestTypeName
Description = $requestTypeDescription
}
}
function Get-QueueIssues {
param(
[Parameter(Mandatory=$true)]
[string]$JiraBase,
[Parameter(Mandatory=$true)]
[string]$ServiceDeskId,
[Parameter(Mandatory=$true)]
[string]$QueueId,
[Parameter(Mandatory=$true)]
[pscustomobject]$AuthContext
)
$headers = @{} + $AuthContext.Headers
$headers["X-Atlassian-Token"] = "no-check"
$headers["User-Agent"] = "PowerShell JiraClient"
$proxyToUse = $AuthContext.ProxyUri
$url = "$JiraBase/rest/servicedeskapi/servicedesk/$ServiceDeskId/queue/$QueueId/issue"
$allIssues = @()
$start = 0
$limit = 50
$page = 0
$activity = "Queue Issues 取得中"
$progressId = 1
do {
$page++
$currentStart = $start
$pagedUrl = "${url}?start=${currentStart}&limit=${limit}"
Write-Progress -Id $progressId -Activity $activity -Status ("ページ: {0} / 取得済み: {1} 件" -f $page, $allIssues.Count) -PercentComplete 0
try {
$response = Invoke-RestMethod -Method Get -Uri $pagedUrl -Headers $headers -Proxy $proxyToUse -ProxyUseDefaultCredentials
$allIssues += $response.values
$count = 0
if ($null -ne $response.values) {
$count = @($response.values).Count
}
Write-Progress -Id $progressId -Activity $activity -Status ("ページ: {0} / start={1} / 今回: {2} 件 / 累計: {3} 件 / isLastPage={4}" -f $page, $currentStart, $count, $allIssues.Count, $response.isLastPage) -CurrentOperation "" -PercentComplete 0
$start += $limit
} catch {
Write-Progress -Id $progressId -Activity $activity -Completed
Write-Host "Issues一覧の取得に失敗しました: $($_.Exception.Message)" -ForegroundColor Red
return $null
}
} while (-not $response.isLastPage)
Write-Progress -Id $progressId -Activity $activity -Completed
return $allIssues
}
function Get-QueueJql {
param(
[Parameter(Mandatory=$true)]
[string]$JiraBase,
[Parameter(Mandatory=$true)]
[string]$ServiceDeskId,
[Parameter(Mandatory=$true)]
[string]$QueueId,
[Parameter(Mandatory=$true)]
[pscustomobject]$AuthContext
)
$headers = @{} + $AuthContext.Headers
$headers["X-Atlassian-Token"] = "no-check"
$headers["User-Agent"] = "PowerShell JiraClient"
$proxyToUse = $AuthContext.ProxyUri
$url = "$JiraBase/rest/servicedeskapi/servicedesk/$ServiceDeskId/queue/$QueueId"
try {
$resp = Invoke-RestMethod -Method Get -Uri $url -Headers $headers -Proxy $proxyToUse -ProxyUseDefaultCredentials
return $resp.jql
} catch {
Write-Host "Queue情報(JQL)の取得に失敗しました: $($_.Exception.Message)" -ForegroundColor Red
return $null
}
}
function Search-JiraIssuesAllPages {
param(
[Parameter(Mandatory=$true)]
[string]$JiraBase,
[Parameter(Mandatory=$true)]
[string]$Jql,
[string[]]$Fields,
[Parameter(Mandatory=$true)]
[pscustomobject]$AuthContext
)
$headers = @{} + $AuthContext.Headers
$headers["X-Atlassian-Token"] = "no-check"
$headers["User-Agent"] = "PowerShell JiraClient"
$proxyToUse = $AuthContext.ProxyUri
$all = @()
$nextPageToken = $null
$maxResults = 100
if ([string]::IsNullOrWhiteSpace($Jql)) {
Write-Host "エラー: JQLが空です(Queueから取得したJQLが空、または加工で消えている可能性があります)。" -ForegroundColor Red
return $null
}
if ($null -eq $Fields -or $Fields.Count -eq 0) {
$Fields = @("summary", "reporter", "created")
}
do {
$url = "$JiraBase/rest/api/3/search/jql"
$bodyParams = [ordered]@{
jql = $Jql
fields = $Fields
maxResults = $maxResults
fieldsByKeys = $true
}
if (-not [string]::IsNullOrWhiteSpace([string]$nextPageToken)) {
$bodyParams.Add("nextPageToken", [string]$nextPageToken)
}
try {
$jsonObj = ([PSCustomObject]$bodyParams) | ConvertTo-Json -Compress -Depth 10 -ErrorAction Stop
if ($jsonObj -is [System.Array]) {
$json = ($jsonObj -join "")
} else {
$json = [string]$jsonObj
}
} catch {
Write-Host "エラー: JSON変換に失敗しました: $($_.Exception.Message)" -ForegroundColor Red
return $null
}
if ([string]::IsNullOrWhiteSpace($json)) {
Write-Host "エラー: JSON変換結果が空です。" -ForegroundColor Red
break
}
$enc = [System.Text.Encoding]::UTF8
$bodyBytes = $enc.GetBytes($json)
Write-Progress -Id 1 -Activity "Jira Search 取得中" -Status ("取得済み={0} 件" -f $all.Count) -PercentComplete 0
try {
$resp = Invoke-RestMethod -Method Post -Uri $url -Headers $headers -Body $bodyBytes -ContentType "application/json; charset=utf-8" -Proxy $proxyToUse -ProxyUseDefaultCredentials
} catch {
Write-Progress -Id 1 -Activity "Jira Search 取得中" -Completed
$errorMsg = "Jira Search の取得に失敗しました: $($_.Exception.Message)"
if ($_.Exception.Response) {
try {
$stream = $_.Exception.Response.GetResponseStream()
if ($stream) {
$reader = [System.IO.StreamReader]::new($stream, [System.Text.Encoding]::UTF8)
$details = $reader.ReadToEnd()
$errorMsg += "`n詳細: $details"
}
} catch {}
}
Write-Host $errorMsg -ForegroundColor Red
break
}
if ($resp.issues) {
$all += $resp.issues
}
$nextPageToken = $resp.nextPageToken
} while ($nextPageToken)
Write-Progress -Id 1 -Activity "Jira Search 取得中" -Completed
return $all
}
function Set-JiraIssueField {
param(
[Parameter(Mandatory=$true)]
[string]$JiraBase,
[Parameter(Mandatory=$true)]
[string]$IssueKey,
[Parameter(Mandatory=$true)]
[string]$Field,
[Parameter(Mandatory=$true)]
$Value,
[Parameter(Mandatory=$true)]
[pscustomobject]$AuthContext
)
if (-not $AuthContext.Headers) {
throw "AuthContext.Headers が未設定です。New-JiraAuthContext の戻り値を指定してください。"
}
if (-not $AuthContext.ProxyUri) {
throw "AuthContext.ProxyUri が未設定です。New-JiraAuthContext の戻り値を指定してください。"
}
$headers = @{} + $AuthContext.Headers
$proxyToUse = $AuthContext.ProxyUri
$url = "$JiraBase/rest/api/3/issue/$IssueKey"
$body = @{fields = @{}}
$body.fields[$Field] = $Value
$json = $body | ConvertTo-Json -Depth 20 -Compress
try {
Invoke-RestMethod -Method Put -Uri $url -Headers $headers -Body $json -ContentType "application/json" -Proxy $proxyToUse -ProxyUseDefaultCredentials | Out-Null
return $true
} catch {
$errorMsg = "Issue更新に失敗しました: $($_.Exception.Message)"
if ($_.Exception.Response) {
try {
$stream = $_.Exception.Response.GetResponseStream()
if ($stream) {
$reader = [System.IO.StreamReader]::new($stream, [System.Text.Encoding]::UTF8)
$details = $reader.ReadToEnd()
if ($details) {
$errorMsg += "`n詳細: $details"
}
}
} catch {}
}
throw $errorMsg
}
}