Public/Add-ServiceNowAttachment.ps1
Function Add-ServiceNowAttachment { <# .SYNOPSIS Attaches a file to an existing record. .DESCRIPTION Attaches a file to an existing record. .PARAMETER Table Name of the table to be queried, by either table name or class name. Use tab completion for list of known tables. You can also provide any table name ad hoc. If using pipeline and this is failing, most likely the table name and class do not match. In this case, provide this value directly. .PARAMETER Id Either the record sys_id or number. If providing just an Id, not with Table, the Id prefix will be looked up to find the table name. .PARAMETER File Path to one or more files to attach .PARAMETER ContentType Content (MIME) type for the file being uploaded. This value will be automatically determined by default, but can be overridden with this parameter. .PARAMETER PassThru Return the newly created attachment details .PARAMETER Connection Azure Automation Connection object containing username, password, and URL for the ServiceNow instance .PARAMETER ServiceNowSession ServiceNow session created by New-ServiceNowSession. Will default to script-level variable $ServiceNowSession. .EXAMPLE Add-ServiceNowAttachment -Id INC0000010 -File @('.\File01.txt', '.\File02.txt') Upload one or more files by record number .EXAMPLE Add-ServiceNowAttachment -Table incident -Id 2306c37c1bafc9100774ebd1b24bcb6d -File @('.\File01.txt', '.\File02.txt') Upload one or more files by record sys_id .EXAMPLE New-ServiceNowIncident @params -PassThru | Add-ServiceNowAttachment -File file01.txt Create a new incident and add an attachment .EXAMPLE Add-ServiceNowAttachment -Id INC0000010 -File file01.txt -ContentType 'text/plain' Upload a file and specify the MIME type (content type). Only required if the function cannot automatically determine the type. .EXAMPLE Add-ServiceNowAttachment -Id INC0000010 -File file01.txt -PassThru Upload a file and receive back the file details .INPUTS Table, ID .OUTPUTS System.Management.Automation.PSCustomObject if -PassThru provided #> [OutputType([PSCustomObject[]])] [CmdletBinding(SupportsShouldProcess)] Param( [Parameter(ValueFromPipelineByPropertyName)] [Alias('sys_class_name')] [string] $Table, [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateScript( { if ($_ -match '^[a-zA-Z0-9]{32}$' -or $_ -match '^([a-zA-Z]+)[0-9]+$') { $true } else { throw 'Id must be either a 32 character alphanumeric, ServiceNow sysid, or prefix/id, ServiceNow number.' } })] [Alias('sys_id', 'SysId', 'number')] [string] $ID, [Parameter(Mandatory)] [ValidateScript( { Test-Path $_ })] [string[]] $File, # Content (MIME) type - if not automatically determined [Parameter()] [string] $ContentType, # Allow the results to be shown [Parameter()] [switch] $PassThru, [Parameter()] [Hashtable] $Connection, [Parameter()] [hashtable] $ServiceNowSession = $script:ServiceNowSession ) begin {} process { if ( $Table ) { $thisTableName = $ServiceNowTable.Where{ $_.ClassName -eq $Table } | Select-Object -ExpandProperty Name if ( -not $thisTableName ) { $thisTableName = $Table } } if ( $ID -match '^[a-zA-Z0-9]{32}$' ) { if ( -not $thisTableName ) { Write-Error 'Providing sys_id for -Id requires a value for -Table. Alternatively, provide an -Id with a prefix, eg. INC1234567, and the table will be automatically determined.' Continue } $thisSysId = $ID } else { if ( -not $thisTableName ) { $thisTable = $ServiceNowTable.Where{ $_.NumberPrefix -and $ID.ToLower().StartsWith($_.NumberPrefix) } if ( $thisTable ) { $thisTableName = $thisTable.Name } else { Write-Error ('The prefix for Id ''{0}'' was not found and the appropriate table cannot be determined. Known prefixes are {1}. Please provide a value for -Table.' -f $ID, ($ServiceNowTable.NumberPrefix.Where( { $_ }) -join ', ')) Continue } } $getParams = @{ Table = $thisTableName Id = $ID Property = 'sys_id' Connection = $Connection ServiceNowSession = $ServiceNowSession } $tableRecord = Get-ServiceNowRecord @getParams if ( -not $tableRecord ) { Write-Error "Record not found for Id '$ID'" continue } $thisSysId = $tableRecord.sys_id } $auth = Get-ServiceNowAuth -C $Connection -S $ServiceNowSession ForEach ($Object in $File) { $FileData = Get-ChildItem $Object -ErrorAction Stop If (-not $ContentType) { # Thanks to https://github.com/samuelneff/MimeTypeMap/blob/master/MimeTypeMap.cs from which # MimeTypeMap.json was adapted $ContentTypeHash = ConvertFrom-Json (Get-Content "$PSScriptRoot\..\config\MimeTypeMap.json" -Raw) $Extension = [IO.Path]::GetExtension($FileData.FullName) $ContentType = $ContentTypeHash.$Extension } # POST: https://instance.service-now.com/api/now/attachment/file?table_name=incident&table_sys_id=d71f7935c0a8016700802b64c67c11c6&file_name=Issue_screenshot # $Uri = "{0}/file?table_name={1}&table_sys_id={2}&file_name={3}" -f $ApiUrl, $Table, $TableSysID, $FileData.Name $invokeRestMethodSplat = $auth $invokeRestMethodSplat.Uri += '/attachment/file?table_name={0}&table_sys_id={1}&file_name={2}' -f $thisTableName, $thisSysId, $FileData.Name $invokeRestMethodSplat.Headers += @{'Content-Type' = $ContentType } $invokeRestMethodSplat.UseBasicParsing = $true $invokeRestMethodSplat += @{ Method = 'POST' InFile = $FileData.FullName } If ($PSCmdlet.ShouldProcess(('{0} {1}' -f $thisTableName, $thisSysId), ('Add attachment {0}' -f $FileData.FullName))) { Write-Verbose ($invokeRestMethodSplat | ConvertTo-Json) $response = Invoke-WebRequest @invokeRestMethodSplat if ( $response.Content ) { if ( $PassThru ) { $content = $response.content | ConvertFrom-Json $content.result } } else { # invoke-webrequest didn't throw an error, but we didn't get content back either throw ('"{0} : {1}' -f $response.StatusCode, $response | Out-String ) } } } } } |